@appsupportkit/support-sdk 0.1.1 → 0.2.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/lib/commonjs/api.js +29 -0
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/components/BottomSheet.js +67 -47
- package/lib/commonjs/components/BottomSheet.js.map +1 -1
- package/lib/commonjs/components/LauncherButton.js +177 -0
- package/lib/commonjs/components/LauncherButton.js.map +1 -0
- package/lib/commonjs/components/SupportButton.js +76 -146
- package/lib/commonjs/components/SupportButton.js.map +1 -1
- package/lib/commonjs/components/icons.js +74 -23
- package/lib/commonjs/components/icons.js.map +1 -1
- package/lib/commonjs/components/primitives.js +183 -38
- package/lib/commonjs/components/primitives.js.map +1 -1
- package/lib/commonjs/components/screens/ComposeScreen.js +115 -58
- package/lib/commonjs/components/screens/ComposeScreen.js.map +1 -1
- package/lib/commonjs/components/screens/HomeScreen.js +170 -115
- package/lib/commonjs/components/screens/HomeScreen.js.map +1 -1
- package/lib/commonjs/components/screens/ThreadScreen.js +222 -108
- package/lib/commonjs/components/screens/ThreadScreen.js.map +1 -1
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/push.js +103 -0
- package/lib/commonjs/push.js.map +1 -0
- package/lib/commonjs/theme.js +136 -17
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/commonjs/types.js +7 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/unread.js +92 -0
- package/lib/commonjs/unread.js.map +1 -0
- package/lib/commonjs/useSupportThreads.js +286 -0
- package/lib/commonjs/useSupportThreads.js.map +1 -0
- package/lib/commonjs/utils.js +33 -0
- package/lib/commonjs/utils.js.map +1 -1
- package/lib/module/api.js +27 -0
- package/lib/module/api.js.map +1 -1
- package/lib/module/components/BottomSheet.js +68 -48
- package/lib/module/components/BottomSheet.js.map +1 -1
- package/lib/module/components/LauncherButton.js +172 -0
- package/lib/module/components/LauncherButton.js.map +1 -0
- package/lib/module/components/SupportButton.js +79 -149
- package/lib/module/components/SupportButton.js.map +1 -1
- package/lib/module/components/icons.js +72 -22
- package/lib/module/components/icons.js.map +1 -1
- package/lib/module/components/primitives.js +179 -39
- package/lib/module/components/primitives.js.map +1 -1
- package/lib/module/components/screens/ComposeScreen.js +118 -61
- package/lib/module/components/screens/ComposeScreen.js.map +1 -1
- package/lib/module/components/screens/HomeScreen.js +172 -117
- package/lib/module/components/screens/HomeScreen.js.map +1 -1
- package/lib/module/components/screens/ThreadScreen.js +228 -112
- package/lib/module/components/screens/ThreadScreen.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/push.js +98 -0
- package/lib/module/push.js.map +1 -0
- package/lib/module/theme.js +135 -16
- package/lib/module/theme.js.map +1 -1
- package/lib/module/types.js +7 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/unread.js +83 -0
- package/lib/module/unread.js.map +1 -0
- package/lib/module/useSupportThreads.js +281 -0
- package/lib/module/useSupportThreads.js.map +1 -0
- package/lib/module/utils.js +30 -0
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/api.d.ts +17 -1
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/components/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/components/LauncherButton.d.ts +14 -0
- package/lib/typescript/components/LauncherButton.d.ts.map +1 -0
- package/lib/typescript/components/SupportButton.d.ts +3 -3
- package/lib/typescript/components/SupportButton.d.ts.map +1 -1
- package/lib/typescript/components/icons.d.ts +13 -2
- package/lib/typescript/components/icons.d.ts.map +1 -1
- package/lib/typescript/components/primitives.d.ts +35 -4
- package/lib/typescript/components/primitives.d.ts.map +1 -1
- package/lib/typescript/components/screens/ComposeScreen.d.ts.map +1 -1
- package/lib/typescript/components/screens/HomeScreen.d.ts +3 -2
- package/lib/typescript/components/screens/HomeScreen.d.ts.map +1 -1
- package/lib/typescript/components/screens/ThreadScreen.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/push.d.ts +18 -0
- package/lib/typescript/push.d.ts.map +1 -0
- package/lib/typescript/theme.d.ts +44 -3
- package/lib/typescript/theme.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +37 -4
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/unread.d.ts +22 -0
- package/lib/typescript/unread.d.ts.map +1 -0
- package/lib/typescript/useSupportThreads.d.ts +28 -0
- package/lib/typescript/useSupportThreads.d.ts.map +1 -0
- package/lib/typescript/utils.d.ts +5 -0
- package/lib/typescript/utils.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/api.ts +41 -1
- package/src/components/BottomSheet.tsx +61 -43
- package/src/components/LauncherButton.tsx +217 -0
- package/src/components/SupportButton.tsx +81 -162
- package/src/components/icons.tsx +77 -19
- package/src/components/primitives.tsx +217 -36
- package/src/components/screens/ComposeScreen.tsx +131 -61
- package/src/components/screens/HomeScreen.tsx +168 -117
- package/src/components/screens/ThreadScreen.tsx +289 -119
- package/src/index.tsx +3 -0
- package/src/push.ts +97 -0
- package/src/theme.ts +165 -18
- package/src/types.ts +39 -4
- package/src/unread.ts +91 -0
- package/src/useSupportThreads.ts +372 -0
- package/src/utils.ts +33 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, type ReactNode } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
|
+
Easing,
|
|
4
5
|
Pressable,
|
|
5
6
|
Text,
|
|
6
7
|
View,
|
|
@@ -25,6 +26,38 @@ export function typeColor(type: QueryType, theme: SupportTheme): string {
|
|
|
25
26
|
return theme.colors.statusOpen;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export function typeBackground(type: QueryType, theme: SupportTheme): string {
|
|
30
|
+
if (type === 'bug_report') return theme.colors.statusDangerBg;
|
|
31
|
+
if (type === 'feature_request') return theme.colors.statusProgressBg;
|
|
32
|
+
return theme.colors.statusOpenBg;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Tinted round chip holding a type icon — the anchor of every list row. */
|
|
36
|
+
export function TypeGlyph({
|
|
37
|
+
type,
|
|
38
|
+
theme,
|
|
39
|
+
size = 36,
|
|
40
|
+
}: {
|
|
41
|
+
type: QueryType;
|
|
42
|
+
theme: SupportTheme;
|
|
43
|
+
size?: number;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<View
|
|
47
|
+
style={{
|
|
48
|
+
width: size,
|
|
49
|
+
height: size,
|
|
50
|
+
borderRadius: size / 2,
|
|
51
|
+
backgroundColor: typeBackground(type, theme),
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{typeIcon(type, typeColor(type, theme), size * 0.44)}
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
28
61
|
/** Status dot + label — same visual language as the dashboard. */
|
|
29
62
|
export function StatusIndicator({
|
|
30
63
|
status,
|
|
@@ -47,7 +80,7 @@ export function StatusIndicator({
|
|
|
47
80
|
);
|
|
48
81
|
}
|
|
49
82
|
|
|
50
|
-
/** Pressable row with the standard
|
|
83
|
+
/** Pressable row with the standard press wash. */
|
|
51
84
|
export function PressableRow({
|
|
52
85
|
onPress,
|
|
53
86
|
children,
|
|
@@ -76,30 +109,83 @@ export function PressableRow({
|
|
|
76
109
|
);
|
|
77
110
|
}
|
|
78
111
|
|
|
112
|
+
/** Circular icon button — back, close, send. */
|
|
113
|
+
export function IconButton({
|
|
114
|
+
onPress,
|
|
115
|
+
children,
|
|
116
|
+
theme,
|
|
117
|
+
accessibilityLabel,
|
|
118
|
+
size = 34,
|
|
119
|
+
disabled,
|
|
120
|
+
tone = 'surface',
|
|
121
|
+
}: {
|
|
122
|
+
onPress: () => void;
|
|
123
|
+
children: ReactNode;
|
|
124
|
+
theme: SupportTheme;
|
|
125
|
+
accessibilityLabel: string;
|
|
126
|
+
size?: number;
|
|
127
|
+
disabled?: boolean;
|
|
128
|
+
tone?: 'surface' | 'accent';
|
|
129
|
+
}) {
|
|
130
|
+
const accent = tone === 'accent';
|
|
131
|
+
return (
|
|
132
|
+
<Pressable
|
|
133
|
+
onPress={onPress}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
hitSlop={6}
|
|
136
|
+
accessibilityRole="button"
|
|
137
|
+
accessibilityLabel={accessibilityLabel}
|
|
138
|
+
style={({ pressed }) => ({
|
|
139
|
+
width: size,
|
|
140
|
+
height: size,
|
|
141
|
+
borderRadius: size / 2,
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
justifyContent: 'center',
|
|
144
|
+
opacity: disabled ? 0.45 : 1,
|
|
145
|
+
backgroundColor: accent
|
|
146
|
+
? pressed
|
|
147
|
+
? theme.colors.accentHover
|
|
148
|
+
: theme.colors.accent
|
|
149
|
+
: pressed
|
|
150
|
+
? theme.colors.surfaceActive
|
|
151
|
+
: theme.colors.surface,
|
|
152
|
+
borderWidth: accent ? 0 : 1,
|
|
153
|
+
borderColor: theme.colors.border,
|
|
154
|
+
})}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</Pressable>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
79
161
|
export function PrimaryButton({
|
|
80
162
|
label,
|
|
81
163
|
onPress,
|
|
82
164
|
disabled,
|
|
83
165
|
theme,
|
|
166
|
+
fullWidth,
|
|
84
167
|
}: {
|
|
85
168
|
label: string;
|
|
86
169
|
onPress: () => void;
|
|
87
170
|
disabled?: boolean;
|
|
88
171
|
theme: SupportTheme;
|
|
172
|
+
fullWidth?: boolean;
|
|
89
173
|
}) {
|
|
90
174
|
return (
|
|
91
175
|
<Pressable
|
|
92
176
|
onPress={onPress}
|
|
93
177
|
disabled={disabled}
|
|
94
178
|
accessibilityRole="button"
|
|
179
|
+
accessibilityState={{ disabled: Boolean(disabled) }}
|
|
95
180
|
style={({ pressed }) => ({
|
|
96
|
-
height:
|
|
97
|
-
|
|
181
|
+
height: 44,
|
|
182
|
+
alignSelf: fullWidth ? 'stretch' : 'flex-start',
|
|
183
|
+
paddingHorizontal: 18,
|
|
98
184
|
borderRadius: theme.radii.control,
|
|
99
185
|
backgroundColor: pressed
|
|
100
186
|
? theme.colors.accentHover
|
|
101
187
|
: theme.colors.accent,
|
|
102
|
-
opacity: disabled ? 0.
|
|
188
|
+
opacity: disabled ? 0.45 : 1,
|
|
103
189
|
alignItems: 'center',
|
|
104
190
|
justifyContent: 'center',
|
|
105
191
|
})}
|
|
@@ -107,8 +193,9 @@ export function PrimaryButton({
|
|
|
107
193
|
<Text
|
|
108
194
|
style={{
|
|
109
195
|
color: theme.colors.accentForeground,
|
|
110
|
-
fontSize:
|
|
111
|
-
fontWeight: '
|
|
196
|
+
fontSize: 14,
|
|
197
|
+
fontWeight: '600',
|
|
198
|
+
letterSpacing: -0.1,
|
|
112
199
|
}}
|
|
113
200
|
>
|
|
114
201
|
{label}
|
|
@@ -131,8 +218,8 @@ export function SecondaryButton({
|
|
|
131
218
|
onPress={onPress}
|
|
132
219
|
accessibilityRole="button"
|
|
133
220
|
style={({ pressed }) => ({
|
|
134
|
-
height:
|
|
135
|
-
paddingHorizontal:
|
|
221
|
+
height: 38,
|
|
222
|
+
paddingHorizontal: 16,
|
|
136
223
|
borderRadius: theme.radii.control,
|
|
137
224
|
borderWidth: 1,
|
|
138
225
|
borderColor: theme.colors.border,
|
|
@@ -143,29 +230,118 @@ export function SecondaryButton({
|
|
|
143
230
|
justifyContent: 'center',
|
|
144
231
|
})}
|
|
145
232
|
>
|
|
146
|
-
<Text style={{ color: theme.colors.textPrimary, fontSize:
|
|
233
|
+
<Text style={{ color: theme.colors.textPrimary, fontSize: 13, fontWeight: '500' }}>
|
|
147
234
|
{label}
|
|
148
235
|
</Text>
|
|
149
236
|
</Pressable>
|
|
150
237
|
);
|
|
151
238
|
}
|
|
152
239
|
|
|
240
|
+
/** The count pill on a thread row. */
|
|
241
|
+
export function UnreadPill({
|
|
242
|
+
count,
|
|
243
|
+
theme,
|
|
244
|
+
}: {
|
|
245
|
+
count: number;
|
|
246
|
+
theme: SupportTheme;
|
|
247
|
+
}) {
|
|
248
|
+
return (
|
|
249
|
+
<View
|
|
250
|
+
style={{
|
|
251
|
+
minWidth: 20,
|
|
252
|
+
height: 20,
|
|
253
|
+
paddingHorizontal: 6,
|
|
254
|
+
borderRadius: theme.radii.pill,
|
|
255
|
+
backgroundColor: theme.colors.accent,
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
justifyContent: 'center',
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
<Text
|
|
261
|
+
style={{
|
|
262
|
+
fontSize: 11,
|
|
263
|
+
lineHeight: 14,
|
|
264
|
+
fontWeight: '700',
|
|
265
|
+
color: theme.colors.accentForeground,
|
|
266
|
+
fontVariant: ['tabular-nums'],
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
{count > 99 ? '99+' : count}
|
|
270
|
+
</Text>
|
|
271
|
+
</View>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function SectionLabel({
|
|
276
|
+
children,
|
|
277
|
+
theme,
|
|
278
|
+
style,
|
|
279
|
+
}: {
|
|
280
|
+
children: ReactNode;
|
|
281
|
+
theme: SupportTheme;
|
|
282
|
+
style?: StyleProp<TextStyle>;
|
|
283
|
+
}) {
|
|
284
|
+
return (
|
|
285
|
+
<Text
|
|
286
|
+
style={[
|
|
287
|
+
{
|
|
288
|
+
fontSize: 11,
|
|
289
|
+
fontWeight: '600',
|
|
290
|
+
textTransform: 'uppercase',
|
|
291
|
+
letterSpacing: 0.6,
|
|
292
|
+
color: theme.colors.textTertiary,
|
|
293
|
+
},
|
|
294
|
+
style,
|
|
295
|
+
]}
|
|
296
|
+
>
|
|
297
|
+
{children}
|
|
298
|
+
</Text>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
153
302
|
/** Small muted icon + one line — no illustrations. */
|
|
154
303
|
export function EmptyState({
|
|
155
304
|
message,
|
|
305
|
+
hint,
|
|
156
306
|
theme,
|
|
157
307
|
icon,
|
|
158
308
|
}: {
|
|
159
309
|
message: string;
|
|
310
|
+
hint?: string;
|
|
160
311
|
theme: SupportTheme;
|
|
161
312
|
icon?: ReactNode;
|
|
162
313
|
}) {
|
|
163
314
|
return (
|
|
164
|
-
<View style={{ alignItems: 'center', paddingVertical:
|
|
165
|
-
|
|
166
|
-
|
|
315
|
+
<View style={{ alignItems: 'center', paddingVertical: 44, paddingHorizontal: 32, gap: 10 }}>
|
|
316
|
+
<View
|
|
317
|
+
style={{
|
|
318
|
+
width: 44,
|
|
319
|
+
height: 44,
|
|
320
|
+
borderRadius: 22,
|
|
321
|
+
alignItems: 'center',
|
|
322
|
+
justifyContent: 'center',
|
|
323
|
+
backgroundColor: theme.colors.surface,
|
|
324
|
+
borderWidth: 1,
|
|
325
|
+
borderColor: theme.colors.border,
|
|
326
|
+
}}
|
|
327
|
+
>
|
|
328
|
+
{icon ?? <ChatIcon color={theme.colors.textTertiary} size={18} />}
|
|
329
|
+
</View>
|
|
330
|
+
<Text style={{ fontSize: 15, fontWeight: '600', color: theme.colors.textPrimary }}>
|
|
167
331
|
{message}
|
|
168
332
|
</Text>
|
|
333
|
+
{hint !== undefined && (
|
|
334
|
+
<Text
|
|
335
|
+
style={{
|
|
336
|
+
fontSize: 13,
|
|
337
|
+
lineHeight: 19,
|
|
338
|
+
color: theme.colors.textSecondary,
|
|
339
|
+
textAlign: 'center',
|
|
340
|
+
}}
|
|
341
|
+
>
|
|
342
|
+
{hint}
|
|
343
|
+
</Text>
|
|
344
|
+
)}
|
|
169
345
|
</View>
|
|
170
346
|
);
|
|
171
347
|
}
|
|
@@ -181,11 +357,11 @@ export function ErrorState({
|
|
|
181
357
|
theme: SupportTheme;
|
|
182
358
|
}) {
|
|
183
359
|
return (
|
|
184
|
-
<View style={{ alignItems: 'center', paddingVertical:
|
|
185
|
-
<Text style={{ fontSize:
|
|
360
|
+
<View style={{ alignItems: 'center', paddingVertical: 40, gap: 12 }}>
|
|
361
|
+
<Text style={{ fontSize: 14, color: theme.colors.textSecondary }}>
|
|
186
362
|
{message}
|
|
187
363
|
</Text>
|
|
188
|
-
<SecondaryButton label="
|
|
364
|
+
<SecondaryButton label="Try again" onPress={onRetry} theme={theme} />
|
|
189
365
|
</View>
|
|
190
366
|
);
|
|
191
367
|
}
|
|
@@ -195,26 +371,30 @@ export function SkeletonBlock({
|
|
|
195
371
|
width,
|
|
196
372
|
height,
|
|
197
373
|
theme,
|
|
374
|
+
radius,
|
|
198
375
|
style,
|
|
199
376
|
}: {
|
|
200
377
|
width: number | `${number}%`;
|
|
201
378
|
height: number;
|
|
202
379
|
theme: SupportTheme;
|
|
380
|
+
radius?: number;
|
|
203
381
|
style?: StyleProp<ViewStyle>;
|
|
204
382
|
}) {
|
|
205
|
-
const opacity = useRef(new Animated.Value(0.
|
|
383
|
+
const opacity = useRef(new Animated.Value(0.4)).current;
|
|
206
384
|
|
|
207
385
|
useEffect(() => {
|
|
208
386
|
const loop = Animated.loop(
|
|
209
387
|
Animated.sequence([
|
|
210
388
|
Animated.timing(opacity, {
|
|
211
|
-
toValue:
|
|
212
|
-
duration:
|
|
389
|
+
toValue: 0.9,
|
|
390
|
+
duration: 700,
|
|
391
|
+
easing: Easing.inOut(Easing.quad),
|
|
213
392
|
useNativeDriver: true,
|
|
214
393
|
}),
|
|
215
394
|
Animated.timing(opacity, {
|
|
216
|
-
toValue: 0.
|
|
217
|
-
duration:
|
|
395
|
+
toValue: 0.4,
|
|
396
|
+
duration: 700,
|
|
397
|
+
easing: Easing.inOut(Easing.quad),
|
|
218
398
|
useNativeDriver: true,
|
|
219
399
|
}),
|
|
220
400
|
]),
|
|
@@ -229,7 +409,7 @@ export function SkeletonBlock({
|
|
|
229
409
|
{
|
|
230
410
|
width,
|
|
231
411
|
height,
|
|
232
|
-
borderRadius: theme.radii.control,
|
|
412
|
+
borderRadius: radius ?? theme.radii.control,
|
|
233
413
|
backgroundColor: theme.colors.surfaceActive,
|
|
234
414
|
opacity,
|
|
235
415
|
},
|
|
@@ -241,22 +421,29 @@ export function SkeletonBlock({
|
|
|
241
421
|
|
|
242
422
|
export const textStyles = {
|
|
243
423
|
title: (theme: SupportTheme): TextStyle => ({
|
|
244
|
-
fontSize:
|
|
424
|
+
fontSize: 19,
|
|
425
|
+
fontWeight: '700',
|
|
426
|
+
letterSpacing: -0.4,
|
|
427
|
+
color: theme.colors.textPrimary,
|
|
428
|
+
}),
|
|
429
|
+
heading: (theme: SupportTheme): TextStyle => ({
|
|
430
|
+
fontSize: 15,
|
|
245
431
|
fontWeight: '600',
|
|
246
432
|
letterSpacing: -0.2,
|
|
247
433
|
color: theme.colors.textPrimary,
|
|
248
434
|
}),
|
|
249
435
|
body: (theme: SupportTheme): TextStyle => ({
|
|
250
|
-
fontSize:
|
|
436
|
+
fontSize: 15,
|
|
251
437
|
color: theme.colors.textPrimary,
|
|
252
|
-
lineHeight:
|
|
438
|
+
lineHeight: 21,
|
|
253
439
|
}),
|
|
254
440
|
secondary: (theme: SupportTheme): TextStyle => ({
|
|
255
441
|
fontSize: 13,
|
|
442
|
+
lineHeight: 19,
|
|
256
443
|
color: theme.colors.textSecondary,
|
|
257
444
|
}),
|
|
258
445
|
meta: (theme: SupportTheme): TextStyle => ({
|
|
259
|
-
fontSize: 11,
|
|
446
|
+
fontSize: 11.5,
|
|
260
447
|
color: theme.colors.textTertiary,
|
|
261
448
|
fontVariant: ['tabular-nums'],
|
|
262
449
|
}),
|
|
@@ -270,27 +457,21 @@ export function TypeChipView({
|
|
|
270
457
|
theme: SupportTheme;
|
|
271
458
|
}) {
|
|
272
459
|
const color = typeColor(type, theme);
|
|
273
|
-
const bg =
|
|
274
|
-
type === 'bug_report'
|
|
275
|
-
? theme.colors.statusDangerBg
|
|
276
|
-
: type === 'feature_request'
|
|
277
|
-
? theme.colors.statusProgressBg
|
|
278
|
-
: theme.colors.statusOpenBg;
|
|
279
460
|
return (
|
|
280
461
|
<View
|
|
281
462
|
style={{
|
|
282
463
|
flexDirection: 'row',
|
|
283
464
|
alignItems: 'center',
|
|
284
465
|
gap: 5,
|
|
285
|
-
paddingHorizontal:
|
|
286
|
-
paddingVertical:
|
|
287
|
-
borderRadius: theme.radii.
|
|
288
|
-
backgroundColor:
|
|
466
|
+
paddingHorizontal: 8,
|
|
467
|
+
paddingVertical: 4,
|
|
468
|
+
borderRadius: theme.radii.pill,
|
|
469
|
+
backgroundColor: typeBackground(type, theme),
|
|
289
470
|
alignSelf: 'flex-start',
|
|
290
471
|
}}
|
|
291
472
|
>
|
|
292
473
|
{typeIcon(type, color, 11)}
|
|
293
|
-
<Text style={{ fontSize: 11, fontWeight: '
|
|
474
|
+
<Text style={{ fontSize: 11.5, fontWeight: '600', color }}>
|
|
294
475
|
{QUERY_TYPE_LABEL[type]}
|
|
295
476
|
</Text>
|
|
296
477
|
</View>
|
|
@@ -1,10 +1,77 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
KeyboardAvoidingView,
|
|
6
|
+
Platform,
|
|
7
|
+
Text,
|
|
8
|
+
TextInput,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
3
11
|
import type { SupportTheme } from '../../theme';
|
|
4
12
|
import type { QueryType } from '../../types';
|
|
5
13
|
import { QUERY_TYPE_LABEL, USER_MESSAGE_MAX, USER_MESSAGE_MIN } from '../../types';
|
|
6
14
|
import { CheckIcon, Chevron } from '../icons';
|
|
7
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
IconButton,
|
|
17
|
+
PrimaryButton,
|
|
18
|
+
textStyles,
|
|
19
|
+
TypeGlyph,
|
|
20
|
+
} from '../primitives';
|
|
21
|
+
|
|
22
|
+
const PROMPT: Record<QueryType, string> = {
|
|
23
|
+
feedback: 'What should we know?',
|
|
24
|
+
feature_request: 'What would you like to see?',
|
|
25
|
+
bug_report: 'What happened, and what did you expect?',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const SUBTITLE: Record<QueryType, string> = {
|
|
29
|
+
feedback: 'Good or bad — it goes straight to the team.',
|
|
30
|
+
feature_request: 'Tell us what you are trying to do.',
|
|
31
|
+
bug_report: 'Steps, screen, and what you expected all help.',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Scales in once on success — the only celebratory motion in the SDK. */
|
|
35
|
+
function SentMark({ theme }: { theme: SupportTheme }) {
|
|
36
|
+
const scale = useRef(new Animated.Value(0.6)).current;
|
|
37
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
Animated.parallel([
|
|
41
|
+
Animated.timing(scale, {
|
|
42
|
+
toValue: 1,
|
|
43
|
+
duration: 320,
|
|
44
|
+
easing: Easing.bezier(0.16, 1, 0.3, 1),
|
|
45
|
+
useNativeDriver: true,
|
|
46
|
+
}),
|
|
47
|
+
Animated.timing(opacity, {
|
|
48
|
+
toValue: 1,
|
|
49
|
+
duration: 200,
|
|
50
|
+
easing: Easing.out(Easing.cubic),
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
}),
|
|
53
|
+
]).start();
|
|
54
|
+
}, [scale, opacity]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Animated.View
|
|
58
|
+
style={{
|
|
59
|
+
width: 56,
|
|
60
|
+
height: 56,
|
|
61
|
+
borderRadius: 28,
|
|
62
|
+
backgroundColor: theme.colors.statusResolvedBg,
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
borderColor: theme.colors.statusResolved,
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
opacity,
|
|
68
|
+
transform: [{ scale }],
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<CheckIcon color={theme.colors.statusResolved} size={20} />
|
|
72
|
+
</Animated.View>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
8
75
|
|
|
9
76
|
export function ComposeScreen({
|
|
10
77
|
theme,
|
|
@@ -27,6 +94,7 @@ export function ComposeScreen({
|
|
|
27
94
|
const valid =
|
|
28
95
|
trimmed.length >= USER_MESSAGE_MIN && trimmed.length <= USER_MESSAGE_MAX;
|
|
29
96
|
const overLimit = message.length > USER_MESSAGE_MAX;
|
|
97
|
+
const tooShort = trimmed.length > 0 && trimmed.length < USER_MESSAGE_MIN;
|
|
30
98
|
|
|
31
99
|
async function handleSubmit() {
|
|
32
100
|
if (!valid || submitting) return;
|
|
@@ -40,25 +108,23 @@ export function ComposeScreen({
|
|
|
40
108
|
|
|
41
109
|
if (sent) {
|
|
42
110
|
return (
|
|
43
|
-
<View
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
>
|
|
55
|
-
<CheckIcon color={theme.colors.statusResolved} size={14} />
|
|
56
|
-
</View>
|
|
57
|
-
<Text style={textStyles.body(theme)}>Sent</Text>
|
|
111
|
+
<View
|
|
112
|
+
style={{
|
|
113
|
+
flex: 1,
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
justifyContent: 'center',
|
|
116
|
+
gap: 14,
|
|
117
|
+
paddingHorizontal: 32,
|
|
118
|
+
paddingBottom: 40,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<SentMark theme={theme} />
|
|
122
|
+
<Text style={textStyles.title(theme)}>Sent</Text>
|
|
58
123
|
<Text style={[textStyles.secondary(theme), { textAlign: 'center' }]}>
|
|
59
|
-
We'll reply here. You'll
|
|
124
|
+
We'll reply right here. You'll find the conversation under
|
|
125
|
+
Open, and the button shows a badge when we answer.
|
|
60
126
|
</Text>
|
|
61
|
-
<View style={{ marginTop:
|
|
127
|
+
<View style={{ marginTop: 6 }}>
|
|
62
128
|
<PrimaryButton label="Done" onPress={onBack} theme={theme} />
|
|
63
129
|
</View>
|
|
64
130
|
</View>
|
|
@@ -70,25 +136,29 @@ export function ComposeScreen({
|
|
|
70
136
|
style={{ flex: 1 }}
|
|
71
137
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
72
138
|
>
|
|
73
|
-
{/* Header: back +
|
|
139
|
+
{/* Header: back + the chosen type */}
|
|
74
140
|
<View
|
|
75
141
|
style={{
|
|
76
142
|
flexDirection: 'row',
|
|
77
143
|
alignItems: 'center',
|
|
78
|
-
gap:
|
|
144
|
+
gap: 10,
|
|
79
145
|
paddingHorizontal: 12,
|
|
80
|
-
paddingBottom:
|
|
146
|
+
paddingBottom: 14,
|
|
81
147
|
}}
|
|
82
148
|
>
|
|
83
|
-
<
|
|
149
|
+
<IconButton
|
|
84
150
|
onPress={onBack}
|
|
85
151
|
theme={theme}
|
|
86
|
-
accessibilityLabel="Back"
|
|
87
|
-
|
|
152
|
+
accessibilityLabel="Back to conversations"
|
|
153
|
+
size={32}
|
|
88
154
|
>
|
|
89
155
|
<Chevron color={theme.colors.textSecondary} direction="left" size={12} />
|
|
90
|
-
</
|
|
91
|
-
<
|
|
156
|
+
</IconButton>
|
|
157
|
+
<TypeGlyph type={type} theme={theme} size={32} />
|
|
158
|
+
<View style={{ flex: 1, gap: 1 }}>
|
|
159
|
+
<Text style={textStyles.heading(theme)}>{QUERY_TYPE_LABEL[type]}</Text>
|
|
160
|
+
<Text style={textStyles.meta(theme)}>{SUBTITLE[type]}</Text>
|
|
161
|
+
</View>
|
|
92
162
|
</View>
|
|
93
163
|
|
|
94
164
|
<View style={{ flex: 1, paddingHorizontal: 16 }}>
|
|
@@ -100,62 +170,62 @@ export function ComposeScreen({
|
|
|
100
170
|
}}
|
|
101
171
|
multiline
|
|
102
172
|
autoFocus
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
? 'What happened, and what did you expect?'
|
|
106
|
-
: type === 'feature_request'
|
|
107
|
-
? 'What would you like to see?'
|
|
108
|
-
: 'What should we know?'
|
|
109
|
-
}
|
|
173
|
+
editable={!submitting}
|
|
174
|
+
placeholder={PROMPT[type]}
|
|
110
175
|
placeholderTextColor={theme.colors.textTertiary}
|
|
111
176
|
style={{
|
|
112
|
-
minHeight:
|
|
113
|
-
maxHeight:
|
|
177
|
+
minHeight: 140,
|
|
178
|
+
maxHeight: 240,
|
|
114
179
|
borderWidth: 1,
|
|
115
180
|
borderColor: theme.colors.border,
|
|
116
|
-
borderRadius: theme.radii.
|
|
181
|
+
borderRadius: theme.radii.card,
|
|
117
182
|
backgroundColor: theme.colors.surface,
|
|
118
183
|
color: theme.colors.textPrimary,
|
|
119
|
-
fontSize:
|
|
120
|
-
lineHeight:
|
|
121
|
-
padding:
|
|
184
|
+
fontSize: 15,
|
|
185
|
+
lineHeight: 21,
|
|
186
|
+
padding: 14,
|
|
122
187
|
textAlignVertical: 'top',
|
|
123
188
|
}}
|
|
124
189
|
/>
|
|
190
|
+
|
|
125
191
|
<View
|
|
126
192
|
style={{
|
|
127
193
|
flexDirection: 'row',
|
|
128
194
|
alignItems: 'center',
|
|
129
|
-
|
|
130
|
-
|
|
195
|
+
gap: 10,
|
|
196
|
+
paddingTop: 10,
|
|
197
|
+
paddingHorizontal: 2,
|
|
198
|
+
minHeight: 20,
|
|
131
199
|
}}
|
|
132
200
|
>
|
|
201
|
+
{failedMessage !== null ? (
|
|
202
|
+
<Text style={{ fontSize: 12, color: theme.colors.statusDanger }}>
|
|
203
|
+
{failedMessage}
|
|
204
|
+
</Text>
|
|
205
|
+
) : tooShort ? (
|
|
206
|
+
<Text style={textStyles.meta(theme)}>
|
|
207
|
+
At least {USER_MESSAGE_MIN} characters
|
|
208
|
+
</Text>
|
|
209
|
+
) : null}
|
|
133
210
|
<Text
|
|
134
211
|
style={[
|
|
135
212
|
textStyles.meta(theme),
|
|
213
|
+
{ marginLeft: 'auto' },
|
|
136
214
|
overLimit && { color: theme.colors.statusDanger },
|
|
137
215
|
]}
|
|
138
216
|
>
|
|
139
217
|
{message.length}/{USER_MESSAGE_MAX}
|
|
140
218
|
</Text>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<View style={{ marginLeft: 'auto' }}>
|
|
152
|
-
<PrimaryButton
|
|
153
|
-
label={submitting ? 'Sending…' : 'Send'}
|
|
154
|
-
onPress={handleSubmit}
|
|
155
|
-
disabled={!valid || submitting}
|
|
156
|
-
theme={theme}
|
|
157
|
-
/>
|
|
158
|
-
</View>
|
|
219
|
+
</View>
|
|
220
|
+
|
|
221
|
+
<View style={{ paddingTop: 12 }}>
|
|
222
|
+
<PrimaryButton
|
|
223
|
+
label={submitting ? 'Sending…' : 'Send'}
|
|
224
|
+
onPress={handleSubmit}
|
|
225
|
+
disabled={!valid || submitting}
|
|
226
|
+
theme={theme}
|
|
227
|
+
fullWidth
|
|
228
|
+
/>
|
|
159
229
|
</View>
|
|
160
230
|
</View>
|
|
161
231
|
</KeyboardAvoidingView>
|