@experiaapp/webchat-react-native 2.0.1
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 +254 -0
- package/app.plugin.js +6 -0
- package/lib/adapters/audio.d.ts +74 -0
- package/lib/adapters/audio.js +39 -0
- package/lib/adapters/audioRoute.d.ts +57 -0
- package/lib/adapters/audioRoute.js +77 -0
- package/lib/adapters/expoDefaults.d.ts +77 -0
- package/lib/adapters/expoDefaults.js +539 -0
- package/lib/adapters/picker.d.ts +67 -0
- package/lib/adapters/picker.js +37 -0
- package/lib/adapters/webrtc.d.ts +131 -0
- package/lib/adapters/webrtc.js +70 -0
- package/lib/core/VideoCallClient.d.ts +106 -0
- package/lib/core/VideoCallClient.js +302 -0
- package/lib/core/WebchatClient.d.ts +34 -0
- package/lib/core/WebchatClient.js +132 -0
- package/lib/core/configClient.d.ts +42 -0
- package/lib/core/configClient.js +302 -0
- package/lib/core/greet.d.ts +11 -0
- package/lib/core/greet.js +17 -0
- package/lib/core/ice.d.ts +31 -0
- package/lib/core/ice.js +48 -0
- package/lib/core/linkify.d.ts +11 -0
- package/lib/core/linkify.js +25 -0
- package/lib/core/logger.d.ts +17 -0
- package/lib/core/logger.js +53 -0
- package/lib/core/media.d.ts +52 -0
- package/lib/core/media.js +115 -0
- package/lib/core/mediaType.d.ts +21 -0
- package/lib/core/mediaType.js +66 -0
- package/lib/core/messagesReducer.d.ts +36 -0
- package/lib/core/messagesReducer.js +58 -0
- package/lib/core/persistence.d.ts +45 -0
- package/lib/core/persistence.js +63 -0
- package/lib/core/socketFactory.d.ts +16 -0
- package/lib/core/socketFactory.js +82 -0
- package/lib/core/types.d.ts +320 -0
- package/lib/core/types.js +30 -0
- package/lib/core/unread.d.ts +2 -0
- package/lib/core/unread.js +5 -0
- package/lib/i18n/ar.json +1 -0
- package/lib/i18n/en.json +1 -0
- package/lib/i18n/index.d.ts +7 -0
- package/lib/i18n/index.js +43 -0
- package/lib/index.d.ts +59 -0
- package/lib/index.js +142 -0
- package/lib/plugin/withWebchat.d.ts +53 -0
- package/lib/plugin/withWebchat.js +164 -0
- package/lib/state/WebchatProvider.d.ts +132 -0
- package/lib/state/WebchatProvider.js +906 -0
- package/lib/state/useWebchat.d.ts +1 -0
- package/lib/state/useWebchat.js +12 -0
- package/lib/theme/dir.d.ts +14 -0
- package/lib/theme/dir.js +20 -0
- package/lib/theme/themeFactory.d.ts +219 -0
- package/lib/theme/themeFactory.js +182 -0
- package/lib/ui/AttachButton.d.ts +35 -0
- package/lib/ui/AttachButton.js +26 -0
- package/lib/ui/AudioRecorder.d.ts +25 -0
- package/lib/ui/AudioRecorder.js +228 -0
- package/lib/ui/Bubble.d.ts +1 -0
- package/lib/ui/Bubble.js +265 -0
- package/lib/ui/CallControls.d.ts +27 -0
- package/lib/ui/CallControls.js +92 -0
- package/lib/ui/CallPlaceholder.d.ts +16 -0
- package/lib/ui/CallPlaceholder.js +73 -0
- package/lib/ui/Composer.d.ts +5 -0
- package/lib/ui/Composer.js +272 -0
- package/lib/ui/FileTile.d.ts +9 -0
- package/lib/ui/FileTile.js +31 -0
- package/lib/ui/Header.d.ts +52 -0
- package/lib/ui/Header.js +236 -0
- package/lib/ui/Icon.d.ts +21 -0
- package/lib/ui/Icon.js +110 -0
- package/lib/ui/ImageBubble.d.ts +11 -0
- package/lib/ui/ImageBubble.js +16 -0
- package/lib/ui/MediaUploadMenu.d.ts +23 -0
- package/lib/ui/MediaUploadMenu.js +68 -0
- package/lib/ui/MessageList.d.ts +1 -0
- package/lib/ui/MessageList.js +46 -0
- package/lib/ui/PoweredBy.d.ts +8 -0
- package/lib/ui/PoweredBy.js +14 -0
- package/lib/ui/PrechatForm.d.ts +1 -0
- package/lib/ui/PrechatForm.js +230 -0
- package/lib/ui/QuickReplies.d.ts +1 -0
- package/lib/ui/QuickReplies.js +24 -0
- package/lib/ui/TypingIndicator.d.ts +9 -0
- package/lib/ui/TypingIndicator.js +88 -0
- package/lib/ui/VideoBubble.d.ts +10 -0
- package/lib/ui/VideoBubble.js +130 -0
- package/lib/ui/VideoCall.d.ts +34 -0
- package/lib/ui/VideoCall.js +191 -0
- package/lib/ui/VideoTile.d.ts +25 -0
- package/lib/ui/VideoTile.js +13 -0
- package/lib/ui/VoiceMessage.d.ts +19 -0
- package/lib/ui/VoiceMessage.js +127 -0
- package/lib/ui/WebChat.d.ts +10 -0
- package/lib/ui/WebChat.js +386 -0
- package/lib/ui/openLink.d.ts +1 -0
- package/lib/ui/openLink.js +16 -0
- package/package.json +94 -0
package/lib/ui/Header.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Header = Header;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const react_native_safe_area_context_1 = require("react-native-safe-area-context");
|
|
7
|
+
const dir_1 = require("../theme/dir");
|
|
8
|
+
const i18n_1 = require("../i18n");
|
|
9
|
+
const Icon_1 = require("./Icon");
|
|
10
|
+
const themeFactory_1 = require("../theme/themeFactory");
|
|
11
|
+
/**
|
|
12
|
+
* Lazily resolve `react-native-svg`. Returns `null` when the optional peer is
|
|
13
|
+
* not installed so the caller renders the styled-View fallback instead of
|
|
14
|
+
* crashing. The require is wrapped so a missing module (or a metro/jest resolve
|
|
15
|
+
* miss) degrades gracefully rather than throwing at render.
|
|
16
|
+
*/
|
|
17
|
+
function loadSvg() {
|
|
18
|
+
try {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
+
const svg = require("react-native-svg");
|
|
21
|
+
if (svg && svg.Svg) {
|
|
22
|
+
return { Svg: svg.Svg, Circle: svg.Circle, Path: svg.Path };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Web's headset glyph (DigitalGovernmentHeader HeadsetIcon), drawn in a 0 0 32 32
|
|
31
|
+
// viewBox with evenodd fill so the ear-cups read as cut-outs.
|
|
32
|
+
const DG_HEADSET_PATH = "M5.01806 14.031C5.34737 8.25166 10.1382 3.66667 16 3.66667C21.8618 3.66667 26.6526 8.25166 26.9819 14.031C25.851 12.9777 24.3341 12.3333 22.6667 12.3333C20.6416 12.3333 19 13.975 19 16V21.3333C19 23.3584 20.6416 25 22.6667 25C24.3355 25 25.8536 24.3545 26.9849 23.2996C26.9766 23.6312 26.9638 23.9278 26.944 24.198C26.8893 24.9429 26.7852 25.4076 26.6182 25.7785C26.1837 26.744 25.4107 27.517 24.4451 27.9516C24.0742 28.1185 23.6095 28.2226 22.8646 28.2773C22.1098 28.3327 21.1485 28.3333 19.7949 28.3333H16C15.4477 28.3333 15 28.7811 15 29.3333C15 29.8856 15.4477 30.3333 16 30.3333H19.8343C21.1398 30.3333 22.1745 30.3333 23.0111 30.2719C23.8661 30.2091 24.5929 30.0783 25.2659 29.7754C26.6771 29.1403 27.8069 28.0105 28.442 26.5993C28.745 25.9262 28.8758 25.1994 28.9386 24.3444C29 23.5078 29 22.4732 29 21.1677V14.6667C29 7.48697 23.1797 1.66667 16 1.66667C8.8203 1.66667 3 7.48697 3 14.6667V18.6667C3 22.1645 5.83553 25 9.33333 25C11.3584 25 13 23.3584 13 21.3333V16C13 13.975 11.3584 12.3333 9.33333 12.3333C7.66594 12.3333 6.14905 12.9777 5.01806 14.031ZM5 18.6667C5 21.0599 6.9401 23 9.33333 23C10.2538 23 11 22.2538 11 21.3333V16C11 15.0795 10.2538 14.3333 9.33333 14.3333C6.9401 14.3333 5 16.2734 5 18.6667ZM27 18.6667C27 16.2734 25.0599 14.3333 22.6667 14.3333C21.7462 14.3333 21 15.0795 21 16V21.3333C21 22.2538 21.7462 23 22.6667 23C25.0599 23 27 21.0599 27 18.6667Z";
|
|
33
|
+
/**
|
|
34
|
+
* No-avatar placeholder — WEB PARITY (DigitalGovernmentHeader's HeadsetIcon): a
|
|
35
|
+
* headset glyph inside a soft grey roundel with a white ring (NOT a "DG" monogram).
|
|
36
|
+
* The svg peer is optional, so the glyph is lazy-required and degrades to a 🎧 Text
|
|
37
|
+
* fallback when react-native-svg is absent.
|
|
38
|
+
*/
|
|
39
|
+
function DigitalGovernmentLogo({ color, size = 40 }) {
|
|
40
|
+
const svg = loadSvg();
|
|
41
|
+
const glyph = Math.round(size * 0.8);
|
|
42
|
+
let inner;
|
|
43
|
+
if (svg) {
|
|
44
|
+
const { Svg, Path } = svg;
|
|
45
|
+
inner = ((0, jsx_runtime_1.jsx)(Svg, { width: glyph, height: glyph, viewBox: "0 0 32 32", children: (0, jsx_runtime_1.jsx)(Path, { d: DG_HEADSET_PATH, fill: color, fillRule: "evenodd" }) }));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
inner = (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { color, fontSize: glyph - 6 }, children: "\uD83C\uDFA7" });
|
|
49
|
+
}
|
|
50
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "dg-logo", accessibilityRole: "image", accessibilityLabel: "agent avatar", style: {
|
|
51
|
+
width: size,
|
|
52
|
+
height: size,
|
|
53
|
+
borderRadius: size / 2,
|
|
54
|
+
backgroundColor: "#F3F4F6",
|
|
55
|
+
borderWidth: 2,
|
|
56
|
+
borderColor: "#FFFFFF",
|
|
57
|
+
alignItems: "center",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
overflow: "hidden",
|
|
60
|
+
}, children: inner }));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Agent avatar (item 25, web parity — Header.tsx renders the agent `avatar` image
|
|
64
|
+
* sized via the headerSectionStyle avatar* keys with an online/offline status dot
|
|
65
|
+
* overlaid). The image is sized/shaped from theme.header.avatar* (width/height/
|
|
66
|
+
* borderRadius/objectFit); the status dot sits on the trailing-bottom corner and
|
|
67
|
+
* tracks connection state with the theme online/offline colors. RN <Image> (no
|
|
68
|
+
* new lib). Only rendered when a url is present — otherwise the caller falls back
|
|
69
|
+
* to the built-in DG logo/monogram.
|
|
70
|
+
*/
|
|
71
|
+
function AgentAvatar({ avatar, theme, connected, }) {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
73
|
+
const h = (_a = theme === null || theme === void 0 ? void 0 : theme.header) !== null && _a !== void 0 ? _a : {};
|
|
74
|
+
const width = (_b = h.avatarWidth) !== null && _b !== void 0 ? _b : 32;
|
|
75
|
+
const height = (_c = h.avatarHeight) !== null && _c !== void 0 ? _c : 32;
|
|
76
|
+
const borderRadius = (_d = h.avatarBorderRadius) !== null && _d !== void 0 ? _d : 16;
|
|
77
|
+
// web object-fit cover/contain maps 1:1 onto RN resizeMode.
|
|
78
|
+
const resizeMode = ((_e = h.avatarObjectFit) !== null && _e !== void 0 ? _e : "cover");
|
|
79
|
+
const dotColor = connected
|
|
80
|
+
? (_f = h.onlineStatus) !== null && _f !== void 0 ? _f : "#085D3A"
|
|
81
|
+
: (_g = h.offlineStatus) !== null && _g !== void 0 ? _g : "#912018";
|
|
82
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { testID: "header-avatar-wrap", style: { width, height }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Image, { testID: "header-avatar", accessibilityRole: "image", accessibilityLabel: (_h = avatar.name) !== null && _h !== void 0 ? _h : "agent avatar", source: { uri: avatar.url }, resizeMode: resizeMode, style: { width, height, borderRadius } }), (0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "header-avatar-dot", style: {
|
|
83
|
+
position: "absolute",
|
|
84
|
+
right: 0,
|
|
85
|
+
bottom: 0,
|
|
86
|
+
width: 10,
|
|
87
|
+
height: 10,
|
|
88
|
+
borderRadius: 5,
|
|
89
|
+
backgroundColor: dotColor,
|
|
90
|
+
borderWidth: 1.5,
|
|
91
|
+
borderColor: "#FFFFFF",
|
|
92
|
+
} })] }));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Online/offline status pill — web parity (DigitalGovernmentHeader renders a
|
|
96
|
+
* rounded pill with a colored dot + "Online"/"Offline" text under the title).
|
|
97
|
+
* Colors come from the theme (onlineStatus / offlineStatus); the pill background
|
|
98
|
+
* is a soft tint so it reads on the colored header bar.
|
|
99
|
+
*/
|
|
100
|
+
function StatusIndicator({ connected, theme, t, position, }) {
|
|
101
|
+
var _a, _b, _c, _d;
|
|
102
|
+
const dot = connected
|
|
103
|
+
? (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.header) === null || _a === void 0 ? void 0 : _a.onlineStatus) !== null && _b !== void 0 ? _b : "#085D3A"
|
|
104
|
+
: (_d = (_c = theme === null || theme === void 0 ? void 0 : theme.header) === null || _c === void 0 ? void 0 : _c.offlineStatus) !== null && _d !== void 0 ? _d : "#912018";
|
|
105
|
+
const pillBg = connected ? "#ECFDF3" : "#FEF3F2";
|
|
106
|
+
const textColor = connected ? "#085D3A" : "#912018";
|
|
107
|
+
// Map the web positional hint onto an RN alignSelf. "right" keeps the pill on
|
|
108
|
+
// the trailing edge, "center" centers it, anything else (incl. the default
|
|
109
|
+
// "top-right" / "left") leaves it on the leading edge under the title.
|
|
110
|
+
const pos = (position !== null && position !== void 0 ? position : "").toLowerCase();
|
|
111
|
+
const alignSelf = pos.includes("right")
|
|
112
|
+
? "flex-end"
|
|
113
|
+
: pos.includes("center")
|
|
114
|
+
? "center"
|
|
115
|
+
: "flex-start";
|
|
116
|
+
// i18n keys online/offline are not in the bundled strings yet; makeT returns
|
|
117
|
+
// the key itself when missing, so fall back to a Capitalized English label.
|
|
118
|
+
const label = (key, fallback) => {
|
|
119
|
+
const s = t(key);
|
|
120
|
+
return s === key ? fallback : s;
|
|
121
|
+
};
|
|
122
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { testID: "header-status", style: {
|
|
123
|
+
backgroundColor: pillBg,
|
|
124
|
+
borderRadius: 9999,
|
|
125
|
+
height: 20,
|
|
126
|
+
flexDirection: "row",
|
|
127
|
+
alignItems: "center",
|
|
128
|
+
alignSelf,
|
|
129
|
+
paddingHorizontal: 8,
|
|
130
|
+
marginTop: 4,
|
|
131
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "header-status-dot", style: {
|
|
132
|
+
width: 10,
|
|
133
|
+
height: 10,
|
|
134
|
+
borderRadius: 5,
|
|
135
|
+
backgroundColor: dot,
|
|
136
|
+
marginEnd: 8,
|
|
137
|
+
} }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { color: textColor, fontSize: 10, fontWeight: "500", lineHeight: 14 }, children: connected ? label("online", "Online") : label("offline", "Offline") })] }));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Connecting banner — web parity (Header renders a configurable "connecting"
|
|
141
|
+
* banner while the socket is not yet up). Text comes from `config.connectingText`;
|
|
142
|
+
* its color/background/alignment/size/weight all read off the theme.header
|
|
143
|
+
* connecting* tokens (web headerSectionStyle connectingText* keys).
|
|
144
|
+
*/
|
|
145
|
+
function ConnectingBanner({ text, theme, dir, }) {
|
|
146
|
+
var _a, _b, _c, _d, _e;
|
|
147
|
+
const h = (_a = theme === null || theme === void 0 ? void 0 : theme.header) !== null && _a !== void 0 ? _a : {};
|
|
148
|
+
const color = (_c = (_b = h.connectingColor) !== null && _b !== void 0 ? _b : h.titleColor) !== null && _c !== void 0 ? _c : "#FFFFFF";
|
|
149
|
+
const bg = (_d = h.connectingBg) !== null && _d !== void 0 ? _d : "#B5B5B5";
|
|
150
|
+
// web alignConnectingText maps onto RN textAlign; default center (web default).
|
|
151
|
+
const align = ((_e = h.connectingAlign) !== null && _e !== void 0 ? _e : "center");
|
|
152
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: "header-connecting", style: { backgroundColor: bg, paddingHorizontal: 12, paddingVertical: 4 }, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: {
|
|
153
|
+
color,
|
|
154
|
+
textAlign: align,
|
|
155
|
+
// undefined size/weight => RN's defaults (web parity: tokens optional).
|
|
156
|
+
fontSize: h.connectingFontSize,
|
|
157
|
+
fontWeight: h.connectingFontWeight,
|
|
158
|
+
writingDirection: dir === "rtl" ? "rtl" : "ltr",
|
|
159
|
+
}, children: text }) }));
|
|
160
|
+
}
|
|
161
|
+
function Header({ variant, dir = "ltr", theme, title, subtitle, language, onClose, showClose = true, onStartCall, connected = false, statusIndicator = true, connectingText, avatar, showAvatar = true, closeImage, }) {
|
|
162
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
163
|
+
const d = (0, dir_1.dirStyles)(dir);
|
|
164
|
+
const t = (0, i18n_1.makeT)(language);
|
|
165
|
+
// Safe-area: keep the bar background under the status bar/notch but push the
|
|
166
|
+
// CONTENT below it (audit: header/video button sat under the status bar).
|
|
167
|
+
const insets = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
|
|
168
|
+
// Header palette comes from the resolved theme (server headerSectionStyle wins),
|
|
169
|
+
// never hardcoded: bg/title/close/video colors all read off theme.header.
|
|
170
|
+
const primary = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _a === void 0 ? void 0 : _a.primary) !== null && _b !== void 0 ? _b : "#0B7A4B";
|
|
171
|
+
const bg = (_d = (_c = theme === null || theme === void 0 ? void 0 : theme.header) === null || _c === void 0 ? void 0 : _c.bg) !== null && _d !== void 0 ? _d : primary;
|
|
172
|
+
const titleColor = (_f = (_e = theme === null || theme === void 0 ? void 0 : theme.header) === null || _e === void 0 ? void 0 : _e.titleColor) !== null && _f !== void 0 ? _f : "#FFFFFF";
|
|
173
|
+
const titleWeight = ((_h = (_g = theme === null || theme === void 0 ? void 0 : theme.header) === null || _g === void 0 ? void 0 : _g.titleWeight) !== null && _h !== void 0 ? _h : "700");
|
|
174
|
+
// Subtitle color/weight are distinct tokens (web subTitleColor / fontWeightSubTitle);
|
|
175
|
+
// both fall back to the title token / 400 when unset (see buildTheme).
|
|
176
|
+
const subTitleColor = (_k = (_j = theme === null || theme === void 0 ? void 0 : theme.header) === null || _j === void 0 ? void 0 : _j.subTitleColor) !== null && _k !== void 0 ? _k : titleColor;
|
|
177
|
+
const subTitleWeight = ((_m = (_l = theme === null || theme === void 0 ? void 0 : theme.header) === null || _l === void 0 ? void 0 : _l.subTitleWeight) !== null && _m !== void 0 ? _m : "400");
|
|
178
|
+
// Tenant custom font (item 28): resolve the loaded RN family for the title /
|
|
179
|
+
// subtitle weight. `undefined` => omit fontFamily (system font, today's look).
|
|
180
|
+
const familyMap = (_o = theme === null || theme === void 0 ? void 0 : theme.font) === null || _o === void 0 ? void 0 : _o.familyMap;
|
|
181
|
+
const titleFontFamily = (0, themeFactory_1.resolveFontFamily)(familyMap, titleWeight);
|
|
182
|
+
const subTitleFontFamily = (0, themeFactory_1.resolveFontFamily)(familyMap, subTitleWeight);
|
|
183
|
+
const closeColor = (_q = (_p = theme === null || theme === void 0 ? void 0 : theme.header) === null || _p === void 0 ? void 0 : _p.closeColor) !== null && _q !== void 0 ? _q : "#FFFFFF";
|
|
184
|
+
const videoColor = (_s = (_r = theme === null || theme === void 0 ? void 0 : theme.header) === null || _r === void 0 ? void 0 : _r.videoColor) !== null && _s !== void 0 ? _s : "#FFFFFF";
|
|
185
|
+
const fontSize = (_u = (_t = theme === null || theme === void 0 ? void 0 : theme.font) === null || _t === void 0 ? void 0 : _t.size) !== null && _u !== void 0 ? _u : 14;
|
|
186
|
+
// Explicit title/subtitle sizes (web fontSizeTitle / fontSizeSubTitle) override
|
|
187
|
+
// the derived size+offset; undefined => keep the historical derived size.
|
|
188
|
+
const titleSizeToken = (_v = theme === null || theme === void 0 ? void 0 : theme.header) === null || _v === void 0 ? void 0 : _v.titleSize;
|
|
189
|
+
const subTitleSize = (_x = (_w = theme === null || theme === void 0 ? void 0 : theme.header) === null || _w === void 0 ? void 0 : _w.subTitleSize) !== null && _x !== void 0 ? _x : fontSize;
|
|
190
|
+
// Status-pill placement (web statusIndicatorPosition); read off the theme token.
|
|
191
|
+
const statusPosition = (_y = theme === null || theme === void 0 ? void 0 : theme.header) === null || _y === void 0 ? void 0 : _y.statusIndicatorPosition;
|
|
192
|
+
// Connecting banner shows only when configured AND the surface is not connected.
|
|
193
|
+
const showConnecting = !!connectingText && !connected;
|
|
194
|
+
// The video-call button sits on the trailing edge, BEFORE the close button —
|
|
195
|
+
// web parity (DigitalGovernmentHeader renders the Join pill then the ✕). Keeps
|
|
196
|
+
// the historical `webchat-start-call` testID so existing call flows still drive
|
|
197
|
+
// it; it just lives in the header now instead of floating over the surface.
|
|
198
|
+
const callButton = onStartCall ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: "webchat-start-call", accessibilityRole: "button", accessibilityLabel: t("start_video_call"), onPress: onStartCall,
|
|
199
|
+
// `color` is carried on the Pressable style purely so consumers/tests can
|
|
200
|
+
// read the resolved video tint off the button (the glyph itself is colored
|
|
201
|
+
// via the Icon below). ViewStyle has no `color` key, hence the cast.
|
|
202
|
+
style: { color: videoColor, paddingHorizontal: 6 }, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "videocam", color: videoColor, size: fontSize + 4 }) })) : null;
|
|
203
|
+
// The close (✕) affordance sits on the trailing edge; under RTL dirStyles' row
|
|
204
|
+
// reversal moves it to the visual leading edge. Gated by showClose (default on).
|
|
205
|
+
// Custom close image (item 29, web `config.closeImage`): when a url is present it
|
|
206
|
+
// replaces the built-in ✕ Icon with an <Image>; otherwise the Icon is used.
|
|
207
|
+
const closeGlyph = (closeImage === null || closeImage === void 0 ? void 0 : closeImage.url) ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { testID: "header-close-image", accessibilityRole: "image", source: { uri: closeImage.url }, resizeMode: "contain", style: { width: fontSize + 6, height: fontSize + 6 } })) : ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "close", color: closeColor, size: fontSize + 6 }));
|
|
208
|
+
const closeButton = showClose && onClose ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: "header-close", accessibilityRole: "button", accessibilityLabel: t("close"), onPress: onClose,
|
|
209
|
+
// `color` carried on the style so consumers/tests can read the resolved
|
|
210
|
+
// close tint off the button; the glyph is colored via the Icon below.
|
|
211
|
+
style: { color: closeColor, paddingHorizontal: 6 }, children: closeGlyph })) : null;
|
|
212
|
+
// Agent avatar (item 25): render the configured avatar image (with status dot) in
|
|
213
|
+
// place of the DG logo when showAvatar && a url is present; else fall back to the
|
|
214
|
+
// built-in logo/monogram.
|
|
215
|
+
const showAgentAvatar = showAvatar && Boolean(avatar === null || avatar === void 0 ? void 0 : avatar.url);
|
|
216
|
+
if (variant === "digitalGovernment") {
|
|
217
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { testID: "header-digitalGovernment", style: { paddingTop: insets.top, backgroundColor: bg }, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flexDirection: d.rowDirection, alignItems: "center", backgroundColor: bg, paddingHorizontal: 12, paddingVertical: 8 }, children: [showAgentAvatar ? ((0, jsx_runtime_1.jsx)(AgentAvatar, { avatar: avatar, theme: theme, connected: connected })) : ((0, jsx_runtime_1.jsx)(DigitalGovernmentLogo, { color: primary })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1, marginHorizontal: 10 }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { accessibilityRole: "header", numberOfLines: 1, style: {
|
|
218
|
+
color: titleColor,
|
|
219
|
+
fontSize: titleSizeToken !== null && titleSizeToken !== void 0 ? titleSizeToken : fontSize + 4,
|
|
220
|
+
fontWeight: titleWeight,
|
|
221
|
+
fontFamily: titleFontFamily,
|
|
222
|
+
textAlign: d.textAlign,
|
|
223
|
+
}, children: title }), subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { testID: "header-subtitle", numberOfLines: 1, style: { color: subTitleColor, fontSize: subTitleSize, fontWeight: subTitleWeight, fontFamily: subTitleFontFamily, textAlign: d.textAlign }, children: subtitle })) : null, statusIndicator ? ((0, jsx_runtime_1.jsx)(StatusIndicator, { connected: connected, theme: theme, t: t, position: statusPosition })) : null] }), callButton, closeButton] }), showConnecting ? ((0, jsx_runtime_1.jsx)(ConnectingBanner, { text: connectingText, theme: theme, dir: dir })) : null] }));
|
|
224
|
+
}
|
|
225
|
+
// experia (default) — the historical flat primary bar, now with the trailing
|
|
226
|
+
// video-call + close buttons in the header (web parity). The bar keeps its
|
|
227
|
+
// testID/paddingTop on the OUTER container so existing assertions hold; the
|
|
228
|
+
// connecting banner (when shown) renders below the bar row.
|
|
229
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { testID: "header-experia", style: { backgroundColor: bg, paddingTop: insets.top + 12 }, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
230
|
+
flexDirection: d.rowDirection,
|
|
231
|
+
alignItems: "center",
|
|
232
|
+
backgroundColor: bg,
|
|
233
|
+
paddingBottom: 12,
|
|
234
|
+
paddingHorizontal: 12,
|
|
235
|
+
}, children: [showAgentAvatar ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { marginEnd: 10 }, children: (0, jsx_runtime_1.jsx)(AgentAvatar, { avatar: avatar, theme: theme, connected: connected }) })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1 }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { accessibilityRole: "header", style: { color: titleColor, fontSize: titleSizeToken !== null && titleSizeToken !== void 0 ? titleSizeToken : fontSize + 2, fontWeight: titleWeight, fontFamily: titleFontFamily, textAlign: d.textAlign }, children: title }), subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { testID: "header-subtitle", numberOfLines: 1, style: { color: subTitleColor, fontSize: subTitleSize, fontWeight: subTitleWeight, fontFamily: subTitleFontFamily, textAlign: d.textAlign }, children: subtitle })) : null, statusIndicator ? ((0, jsx_runtime_1.jsx)(StatusIndicator, { connected: connected, theme: theme, t: t, position: statusPosition })) : null] }), callButton, closeButton] }), showConnecting ? ((0, jsx_runtime_1.jsx)(ConnectingBanner, { text: connectingText, theme: theme, dir: dir })) : null] }));
|
|
236
|
+
}
|
package/lib/ui/Icon.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The semantic icon set we need across the chat + call surfaces. Each maps to a
|
|
3
|
+
* Material icon shape (see PATHS below). Adding an icon = add a name here + a
|
|
4
|
+
* path/glyph entry; consumers get the new option type-checked for free.
|
|
5
|
+
*/
|
|
6
|
+
export type IconName = "chat" | "send" | "attach" | "image" | "document" | "emoji" | "mic" | "micOff" | "videocam" | "videocamOff" | "callEnd" | "close" | "back" | "cameraSwitch" | "speaker" | "speakerOff" | "play" | "pause" | "stop" | "thumbUp" | "thumbDown";
|
|
7
|
+
export interface IconProps {
|
|
8
|
+
/** which Material shape to draw */
|
|
9
|
+
name: IconName;
|
|
10
|
+
/** fill color for the path (and color of the Text fallback) */
|
|
11
|
+
color?: string;
|
|
12
|
+
/** width === height in px; also the fallback font size. Default 24. */
|
|
13
|
+
size?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A themeable Material-style icon. Pass a semantic `name` plus `color`/`size`.
|
|
17
|
+
* Renders an SVG path when `react-native-svg` is available, otherwise a tiny
|
|
18
|
+
* Text glyph in the same color/size — never throws on a missing peer.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Icon({ name, color, size }: IconProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export default Icon;
|
package/lib/ui/Icon.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Icon = Icon;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
/**
|
|
7
|
+
* Standard 24x24 Material `d` path data (reasonable approximations). Each renders
|
|
8
|
+
* inside a 0 0 24 24 viewBox so `size` scales it uniformly.
|
|
9
|
+
*/
|
|
10
|
+
const PATHS = {
|
|
11
|
+
// chat — speech bubble (Material `chat-bubble`)
|
|
12
|
+
chat: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z",
|
|
13
|
+
// send — paper plane (Material `send`)
|
|
14
|
+
send: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z",
|
|
15
|
+
// attach — paperclip (Material `attach-file`)
|
|
16
|
+
attach: "M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5S13.5 3.62 13.5 5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z",
|
|
17
|
+
// image — landscape with sun (Material `image`)
|
|
18
|
+
image: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z",
|
|
19
|
+
// document — file with folded corner (Material `insert-drive-file`)
|
|
20
|
+
document: "M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z",
|
|
21
|
+
// emoji — insert-emoticon (outline)
|
|
22
|
+
emoji: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z",
|
|
23
|
+
// mic — microphone (Material `mic`)
|
|
24
|
+
mic: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5-3c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z",
|
|
25
|
+
// micOff — microphone with slash (Material `mic-off`)
|
|
26
|
+
micOff: "M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z",
|
|
27
|
+
// videocam — video camera (Material `videocam`)
|
|
28
|
+
videocam: "M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z",
|
|
29
|
+
// videocamOff — video camera with slash (Material `videocam-off`)
|
|
30
|
+
videocamOff: "M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z",
|
|
31
|
+
// callEnd — call-end handset (Material `call-end`)
|
|
32
|
+
callEnd: "M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08c-.18-.17-.29-.42-.29-.7 0-.28.11-.53.29-.71C3.34 8.78 7.46 7 12 7s8.66 1.78 11.71 4.67c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28-.79-.74-1.69-1.36-2.67-1.85-.33-.16-.56-.5-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z",
|
|
33
|
+
// close — x (Material `close`)
|
|
34
|
+
close: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
|
|
35
|
+
// back — arrow-back (Material `arrow-back`)
|
|
36
|
+
back: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z",
|
|
37
|
+
// cameraSwitch — flip-camera (Material `flip-camera-android` approximation)
|
|
38
|
+
cameraSwitch: "M12 5c-3.31 0-6 2.69-6 6 0 1.01.25 1.97.7 2.8L5.24 15.26C4.46 14.03 4 12.57 4 11c0-4.42 3.58-8 8-8V0l4 4-4 4V5zm6.76.74L17.3 7.2c.45.83.7 1.79.7 2.8 0 4.42-3.58 8-8 8v3l-4-4 4-4v3c3.31 0 6-2.69 6-6 0-1.57-.46-3.03-1.24-4.26z",
|
|
39
|
+
// speaker — volume-up (Material `volume-up`)
|
|
40
|
+
speaker: "M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z",
|
|
41
|
+
// speakerOff — volume-off (Material `volume-off`)
|
|
42
|
+
speakerOff: "M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z",
|
|
43
|
+
// play — play arrow (Material `play-arrow`)
|
|
44
|
+
play: "M8 5v14l11-7z",
|
|
45
|
+
// pause — two bars (Material `pause`)
|
|
46
|
+
pause: "M6 19h4V5H6v14zm8-14v14h4V5h-4z",
|
|
47
|
+
// stop — square (Material `stop`)
|
|
48
|
+
stop: "M6 6h12v12H6z",
|
|
49
|
+
// thumbUp — like, filled (Material `thumb-up`)
|
|
50
|
+
thumbUp: "M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z",
|
|
51
|
+
// thumbDown — dislike, filled (Material `thumb-down`)
|
|
52
|
+
thumbDown: "M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z",
|
|
53
|
+
};
|
|
54
|
+
/** Minimal single-char fallback glyphs (used only when react-native-svg is absent). */
|
|
55
|
+
const GLYPHS = {
|
|
56
|
+
chat: "💬",
|
|
57
|
+
send: "➤",
|
|
58
|
+
attach: "📎",
|
|
59
|
+
image: "🖼",
|
|
60
|
+
document: "📄",
|
|
61
|
+
emoji: "🙂",
|
|
62
|
+
mic: "🎤",
|
|
63
|
+
micOff: "🔇",
|
|
64
|
+
videocam: "📹",
|
|
65
|
+
videocamOff: "📷",
|
|
66
|
+
callEnd: "📞",
|
|
67
|
+
close: "✕",
|
|
68
|
+
back: "‹",
|
|
69
|
+
cameraSwitch: "🔄",
|
|
70
|
+
speaker: "🔊",
|
|
71
|
+
speakerOff: "🔈",
|
|
72
|
+
play: "▶",
|
|
73
|
+
pause: "⏸",
|
|
74
|
+
stop: "⏹",
|
|
75
|
+
thumbUp: "👍",
|
|
76
|
+
thumbDown: "👎",
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Lazily resolve `react-native-svg`. Returns `null` when the optional peer is
|
|
80
|
+
* not installed (or fails to resolve) so the caller renders the <Text> glyph
|
|
81
|
+
* fallback instead of crashing. Mirrors Header.tsx's loadSvg().
|
|
82
|
+
*/
|
|
83
|
+
function loadSvg() {
|
|
84
|
+
try {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
86
|
+
const svg = require("react-native-svg");
|
|
87
|
+
if (svg && svg.Svg && svg.Path) {
|
|
88
|
+
return { Svg: svg.Svg, Path: svg.Path };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A themeable Material-style icon. Pass a semantic `name` plus `color`/`size`.
|
|
98
|
+
* Renders an SVG path when `react-native-svg` is available, otherwise a tiny
|
|
99
|
+
* Text glyph in the same color/size — never throws on a missing peer.
|
|
100
|
+
*/
|
|
101
|
+
function Icon({ name, color = "currentColor", size = 24 }) {
|
|
102
|
+
const svg = loadSvg();
|
|
103
|
+
if (svg) {
|
|
104
|
+
const { Svg, Path } = svg;
|
|
105
|
+
return ((0, jsx_runtime_1.jsx)(Svg, { testID: `icon-${name}`, width: size, height: size, viewBox: "0 0 24 24", accessibilityRole: "image", children: (0, jsx_runtime_1.jsx)(Path, { d: PATHS[name], fill: color }) }));
|
|
106
|
+
}
|
|
107
|
+
// Fallback: a single-char glyph sized to match (no native svg dependency).
|
|
108
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { testID: `icon-${name}`, accessibilityRole: "image", style: { color, fontSize: size, lineHeight: size }, children: GLYPHS[name] }));
|
|
109
|
+
}
|
|
110
|
+
exports.default = Icon;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ImageBubbleProps {
|
|
2
|
+
/** hosted URL or data-URL for the image */
|
|
3
|
+
uri: string;
|
|
4
|
+
/** alt/label for screen readers */
|
|
5
|
+
name?: string;
|
|
6
|
+
theme: any;
|
|
7
|
+
testID?: string;
|
|
8
|
+
/** optional: override the default tap-to-enlarge behavior */
|
|
9
|
+
onPress?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function ImageBubble({ uri, name, theme, testID, onPress }: ImageBubbleProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImageBubble = ImageBubble;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
// src/ui/ImageBubble.tsx
|
|
6
|
+
// Received-image preview. Source is the hosted mediaUrl (after UPDATE_MSG) or a
|
|
7
|
+
// data-URL fallback. Tap-to-enlarge is optional and opt-in via onPress.
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const react_native_1 = require("react-native");
|
|
10
|
+
function ImageBubble({ uri, name, theme, testID, onPress }) {
|
|
11
|
+
var _a;
|
|
12
|
+
const [enlarged, setEnlarged] = (0, react_1.useState)(false);
|
|
13
|
+
const label = name || "image attachment";
|
|
14
|
+
const handlePress = onPress !== null && onPress !== void 0 ? onPress : (() => setEnlarged(true));
|
|
15
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { testID: testID, children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: testID ? `${testID}-press` : undefined, accessibilityRole: "imagebutton", accessibilityLabel: label, onPress: handlePress, children: (0, jsx_runtime_1.jsx)(react_native_1.Image, { testID: testID ? `${testID}-image` : undefined, accessibilityLabel: label, source: { uri }, resizeMode: "cover", style: { width: 200, height: 150, borderRadius: 8, backgroundColor: (_a = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _a === void 0 ? void 0 : _a.surface } }) }), !onPress && ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: enlarged, transparent: true, animationType: "fade", onRequestClose: () => setEnlarged(false), children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: testID ? `${testID}-overlay` : undefined, onPress: () => setEnlarged(false), style: { flex: 1, backgroundColor: "rgba(0,0,0,0.85)", justifyContent: "center", alignItems: "center" }, children: (0, jsx_runtime_1.jsx)(react_native_1.Image, { accessibilityLabel: label, source: { uri }, resizeMode: "contain", style: { width: "100%", height: "80%" } }) }) }))] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IconName } from "./Icon";
|
|
2
|
+
export interface MediaUploadOption {
|
|
3
|
+
/** stable key + React key */
|
|
4
|
+
key: "image" | "video" | "document" | "audio";
|
|
5
|
+
/** themed leading icon */
|
|
6
|
+
icon: IconName;
|
|
7
|
+
/** localized row label */
|
|
8
|
+
label: string;
|
|
9
|
+
/** RNTL query handle, e.g. "media-option-image" */
|
|
10
|
+
testID: string;
|
|
11
|
+
/** invoked when the row is pressed (the menu also closes) */
|
|
12
|
+
onPress: () => void;
|
|
13
|
+
}
|
|
14
|
+
export interface MediaUploadMenuProps {
|
|
15
|
+
visible: boolean;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
options: MediaUploadOption[];
|
|
18
|
+
dir: "ltr" | "rtl";
|
|
19
|
+
theme: any;
|
|
20
|
+
language?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function MediaUploadMenu({ visible, onClose, options, dir, theme, language, }: MediaUploadMenuProps): import("react/jsx-runtime").JSX.Element | null;
|
|
23
|
+
export default MediaUploadMenu;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MediaUploadMenu = MediaUploadMenu;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const dir_1 = require("../theme/dir");
|
|
7
|
+
const i18n_1 = require("../i18n");
|
|
8
|
+
const Icon_1 = require("./Icon");
|
|
9
|
+
// Reads the same server-driven send tokens the Composer uses, defensively (unit
|
|
10
|
+
// tests pass a minimal { colors, font } theme). Mirrors Composer.sendTokens.
|
|
11
|
+
function tokens(theme) {
|
|
12
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
13
|
+
const s = (_a = theme === null || theme === void 0 ? void 0 : theme.send) !== null && _a !== void 0 ? _a : {};
|
|
14
|
+
const primary = (_c = (_b = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _b === void 0 ? void 0 : _b.primary) !== null && _c !== void 0 ? _c : "#1B8354";
|
|
15
|
+
return {
|
|
16
|
+
surface: (_e = (_d = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _d === void 0 ? void 0 : _d.surface) !== null && _e !== void 0 ? _e : "#FCFCFD",
|
|
17
|
+
text: (_h = (_f = s.fieldColor) !== null && _f !== void 0 ? _f : (_g = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _g === void 0 ? void 0 : _g.text) !== null && _h !== void 0 ? _h : "#2d2d2d",
|
|
18
|
+
radius: (_j = s.fieldRadius) !== null && _j !== void 0 ? _j : 4,
|
|
19
|
+
iconColor: (_k = s.iconColor) !== null && _k !== void 0 ? _k : primary,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function MediaUploadMenu({ visible, onClose, options, dir, theme, language, }) {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const d = (0, dir_1.dirStyles)(dir);
|
|
25
|
+
const t = (0, i18n_1.makeT)(language);
|
|
26
|
+
const k = tokens(theme);
|
|
27
|
+
const fontSize = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.font) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 14;
|
|
28
|
+
if (!visible)
|
|
29
|
+
return null;
|
|
30
|
+
const select = (opt) => {
|
|
31
|
+
opt.onPress();
|
|
32
|
+
onClose();
|
|
33
|
+
};
|
|
34
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: visible, transparent: true, animationType: "fade", onRequestClose: onClose, children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: "media-menu-backdrop", accessibilityRole: "button", accessibilityLabel: t("close"), onPress: onClose, style: {
|
|
35
|
+
flex: 1,
|
|
36
|
+
backgroundColor: "rgba(0,0,0,0.35)",
|
|
37
|
+
justifyContent: "flex-end",
|
|
38
|
+
}, children: (0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { testID: "media-menu-sheet", accessibilityViewIsModal: true, accessibilityRole: "menu", onPress: () => { }, style: {
|
|
39
|
+
backgroundColor: k.surface,
|
|
40
|
+
borderTopLeftRadius: 12,
|
|
41
|
+
borderTopRightRadius: 12,
|
|
42
|
+
paddingVertical: 8,
|
|
43
|
+
}, children: [options.map((opt) => ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { testID: opt.testID, accessibilityRole: "menuitem", accessibilityLabel: opt.label, onPress: () => select(opt), style: {
|
|
44
|
+
flexDirection: d.rowDirection,
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
gap: 12,
|
|
47
|
+
paddingVertical: 14,
|
|
48
|
+
paddingHorizontal: 20,
|
|
49
|
+
}, children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: opt.icon, color: k.iconColor, size: fontSize + 8 }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
50
|
+
color: k.text,
|
|
51
|
+
fontSize: fontSize + 2,
|
|
52
|
+
textAlign: d.textAlign,
|
|
53
|
+
flex: 1,
|
|
54
|
+
}, children: opt.label })] }, opt.key))), (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: "media-menu-cancel", accessibilityRole: "button", accessibilityLabel: t("cancel"), onPress: onClose, style: {
|
|
55
|
+
flexDirection: d.rowDirection,
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
paddingVertical: 14,
|
|
58
|
+
paddingHorizontal: 20,
|
|
59
|
+
borderTopWidth: 1,
|
|
60
|
+
borderTopColor: "rgba(0,0,0,0.08)",
|
|
61
|
+
}, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
62
|
+
color: k.iconColor,
|
|
63
|
+
fontSize: fontSize + 2,
|
|
64
|
+
textAlign: d.textAlign,
|
|
65
|
+
flex: 1,
|
|
66
|
+
}, children: t("cancel") }) })] }) }) }));
|
|
67
|
+
}
|
|
68
|
+
exports.default = MediaUploadMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function MessageList({ messages, dir, theme, send, audioAdapter, react, showMessageDate, showTyping, showAvatar, avatar, }: any): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageList = MessageList;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const Bubble_1 = require("./Bubble");
|
|
7
|
+
const TypingIndicator_1 = require("./TypingIndicator");
|
|
8
|
+
const useWebchat_1 = require("../state/useWebchat");
|
|
9
|
+
// MessageList owns the quick-reply -> send wiring (audit #3): selecting a chip
|
|
10
|
+
// sends { text: title, payload } so the title shows in the user echo while the
|
|
11
|
+
// backend receives the payload as the message body.
|
|
12
|
+
//
|
|
13
|
+
// Per the context contract, UI consumers read live values from useWebchat();
|
|
14
|
+
// `send` (and the optional received-voice `audioAdapter`) are sourced from there
|
|
15
|
+
// so the wiring stays live regardless of which props the surface threads down.
|
|
16
|
+
// Explicit props still win (testability / override).
|
|
17
|
+
//
|
|
18
|
+
// Typing indicator (item 23): rendered at the visual BOTTOM of the thread (the
|
|
19
|
+
// list is `inverted`, so the header slot is the bottom) when `showTyping` is true.
|
|
20
|
+
// The Surface gates that on `resolvedConfig.displayTypingIndication && awaitingReply`
|
|
21
|
+
// (web Body/index.tsx:134); avatar/showAvatar thread to each received Bubble (item 25).
|
|
22
|
+
function MessageList({ messages, dir, theme, send, audioAdapter, react, showMessageDate = true, showTyping = false, showAvatar = false, avatar, }) {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
const ctx = (0, useWebchat_1.useWebchat)();
|
|
25
|
+
const doSend = send !== null && send !== void 0 ? send : ctx.send;
|
|
26
|
+
const adapter = audioAdapter !== null && audioAdapter !== void 0 ? audioAdapter : ctx.audioAdapter;
|
|
27
|
+
// Feedback handler sourced from context like send (Section 4); an explicit prop wins.
|
|
28
|
+
const doReact = react !== null && react !== void 0 ? react : ctx.react;
|
|
29
|
+
// Web body sits on a flat chat background (theme.chat.bg / web bgChatColor) — the
|
|
30
|
+
// list inherits it so a white received bubble reads as a card on the surface
|
|
31
|
+
// rather than blending in. Falls back to the surface colour for legacy themes.
|
|
32
|
+
const chatBg = (_d = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.chat) === null || _a === void 0 ? void 0 : _a.bg) !== null && _b !== void 0 ? _b : (_c = theme === null || theme === void 0 ? void 0 : theme.colors) === null || _c === void 0 ? void 0 : _c.surface) !== null && _d !== void 0 ? _d : "#FFFFFF";
|
|
33
|
+
const data = [...messages].reverse();
|
|
34
|
+
const onQuickReply = (q) => doSend === null || doSend === void 0 ? void 0 : doSend({ text: q.title, payload: q.payload });
|
|
35
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.FlatList, { inverted: true, data: data,
|
|
36
|
+
// Suffix with the index so a duplicate client `key` (legacy persisted data)
|
|
37
|
+
// can never produce a duplicate React key. Messages are append-only here, so
|
|
38
|
+
// the index is stable for existing rows.
|
|
39
|
+
keyExtractor: (m, i) => `${m.key}-${i}`, style: { backgroundColor: chatBg },
|
|
40
|
+
// Vertical breathing room between bubbles + edge padding (web pads each
|
|
41
|
+
// message row ~20px and the body bottom ~10px).
|
|
42
|
+
contentContainerStyle: { paddingVertical: 10 }, ItemSeparatorComponent: () => (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { height: 12 } }),
|
|
43
|
+
// The list is inverted, so ListHeaderComponent renders at the visual BOTTOM —
|
|
44
|
+
// exactly where the typing bubble belongs (newest, under the last message).
|
|
45
|
+
ListHeaderComponent: showTyping ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { paddingBottom: 12 }, children: (0, jsx_runtime_1.jsx)(TypingIndicator_1.TypingIndicator, { theme: theme, dir: dir }) })) : null, renderItem: ({ item }) => ((0, jsx_runtime_1.jsx)(Bubble_1.Bubble, { message: item, dir: dir, theme: theme, audioAdapter: adapter, onQuickReply: onQuickReply, react: doReact, showMessageDate: showMessageDate, showAvatar: showAvatar, avatar: avatar })) }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface PoweredByProps {
|
|
2
|
+
theme: any;
|
|
3
|
+
/** label text (web localized `powered_experia`; default English copy). */
|
|
4
|
+
label?: string;
|
|
5
|
+
testID?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function PoweredBy({ theme, label, testID }: PoweredByProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default PoweredBy;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PoweredBy = PoweredBy;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const openLink_1 = require("./openLink");
|
|
7
|
+
const POWERED_URL = "https://experiaapp.com/";
|
|
8
|
+
function PoweredBy({ theme, label = "Powered by Experia", testID = "powered-by" }) {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
// muted label color: prefer a dedicated chat date color, else a neutral grey.
|
|
11
|
+
const color = (_b = (_a = theme === null || theme === void 0 ? void 0 : theme.chat) === null || _a === void 0 ? void 0 : _a.dateColor) !== null && _b !== void 0 ? _b : "#9AA0A6";
|
|
12
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: testID, style: { alignItems: "center", justifyContent: "center", paddingVertical: 8 }, children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { accessibilityRole: "link", accessibilityLabel: label, onPress: () => (0, openLink_1.openLink)(POWERED_URL), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: { color, fontSize: 12 }, children: label }) }) }));
|
|
13
|
+
}
|
|
14
|
+
exports.default = PoweredBy;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function PrechatForm({ fields, onSubmit, dir, theme, headline, submitLabel, language, }: any): import("react/jsx-runtime").JSX.Element;
|