@webority/mobile-support 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/lib/commonjs/SupportProvider.js +335 -0
  4. package/lib/commonjs/client.js +164 -0
  5. package/lib/commonjs/clipboard.js +10 -0
  6. package/lib/commonjs/components/Attachments.js +246 -0
  7. package/lib/commonjs/components/NewSupportTicket.js +210 -0
  8. package/lib/commonjs/components/PhotoTray.js +141 -0
  9. package/lib/commonjs/components/SupportContact.js +134 -0
  10. package/lib/commonjs/components/SupportTicketDetail.js +348 -0
  11. package/lib/commonjs/components/SupportTicketList.js +279 -0
  12. package/lib/commonjs/device.js +29 -0
  13. package/lib/commonjs/files.js +37 -0
  14. package/lib/commonjs/format.js +29 -0
  15. package/lib/commonjs/index.js +97 -0
  16. package/lib/commonjs/package.json +1 -0
  17. package/lib/commonjs/photos.js +67 -0
  18. package/lib/commonjs/report/ReportIssueButton.js +57 -0
  19. package/lib/commonjs/report/captureAndReport.js +34 -0
  20. package/lib/commonjs/report/index.js +20 -0
  21. package/lib/commonjs/share/index.js +44 -0
  22. package/lib/commonjs/types.js +2 -0
  23. package/lib/commonjs/validation.js +45 -0
  24. package/lib/module/SupportProvider.js +323 -0
  25. package/lib/module/client.js +156 -0
  26. package/lib/module/clipboard.js +5 -0
  27. package/lib/module/components/Attachments.js +240 -0
  28. package/lib/module/components/NewSupportTicket.js +205 -0
  29. package/lib/module/components/PhotoTray.js +136 -0
  30. package/lib/module/components/SupportContact.js +129 -0
  31. package/lib/module/components/SupportTicketDetail.js +343 -0
  32. package/lib/module/components/SupportTicketList.js +273 -0
  33. package/lib/module/device.js +24 -0
  34. package/lib/module/files.js +31 -0
  35. package/lib/module/format.js +23 -0
  36. package/lib/module/index.js +10 -0
  37. package/lib/module/package.json +1 -0
  38. package/lib/module/photos.js +61 -0
  39. package/lib/module/report/ReportIssueButton.js +52 -0
  40. package/lib/module/report/captureAndReport.js +29 -0
  41. package/lib/module/report/index.js +12 -0
  42. package/lib/module/share/index.js +37 -0
  43. package/lib/module/types.js +2 -0
  44. package/lib/module/validation.js +38 -0
  45. package/lib/typescript/commonjs/SupportProvider.d.ts +82 -0
  46. package/lib/typescript/commonjs/client.d.ts +63 -0
  47. package/lib/typescript/commonjs/clipboard.d.ts +3 -0
  48. package/lib/typescript/commonjs/components/Attachments.d.ts +16 -0
  49. package/lib/typescript/commonjs/components/NewSupportTicket.d.ts +12 -0
  50. package/lib/typescript/commonjs/components/PhotoTray.d.ts +17 -0
  51. package/lib/typescript/commonjs/components/SupportContact.d.ts +7 -0
  52. package/lib/typescript/commonjs/components/SupportTicketDetail.d.ts +7 -0
  53. package/lib/typescript/commonjs/components/SupportTicketList.d.ts +28 -0
  54. package/lib/typescript/commonjs/device.d.ts +6 -0
  55. package/lib/typescript/commonjs/files.d.ts +10 -0
  56. package/lib/typescript/commonjs/format.d.ts +10 -0
  57. package/lib/typescript/commonjs/index.d.ts +15 -0
  58. package/lib/typescript/commonjs/package.json +1 -0
  59. package/lib/typescript/commonjs/photos.d.ts +16 -0
  60. package/lib/typescript/commonjs/report/ReportIssueButton.d.ts +14 -0
  61. package/lib/typescript/commonjs/report/captureAndReport.d.ts +15 -0
  62. package/lib/typescript/commonjs/report/index.d.ts +11 -0
  63. package/lib/typescript/commonjs/share/index.d.ts +10 -0
  64. package/lib/typescript/commonjs/types.d.ts +152 -0
  65. package/lib/typescript/commonjs/validation.d.ts +10 -0
  66. package/lib/typescript/module/SupportProvider.d.ts +82 -0
  67. package/lib/typescript/module/client.d.ts +63 -0
  68. package/lib/typescript/module/clipboard.d.ts +3 -0
  69. package/lib/typescript/module/components/Attachments.d.ts +16 -0
  70. package/lib/typescript/module/components/NewSupportTicket.d.ts +12 -0
  71. package/lib/typescript/module/components/PhotoTray.d.ts +17 -0
  72. package/lib/typescript/module/components/SupportContact.d.ts +7 -0
  73. package/lib/typescript/module/components/SupportTicketDetail.d.ts +7 -0
  74. package/lib/typescript/module/components/SupportTicketList.d.ts +28 -0
  75. package/lib/typescript/module/device.d.ts +6 -0
  76. package/lib/typescript/module/files.d.ts +10 -0
  77. package/lib/typescript/module/format.d.ts +10 -0
  78. package/lib/typescript/module/index.d.ts +15 -0
  79. package/lib/typescript/module/package.json +1 -0
  80. package/lib/typescript/module/photos.d.ts +16 -0
  81. package/lib/typescript/module/report/ReportIssueButton.d.ts +14 -0
  82. package/lib/typescript/module/report/captureAndReport.d.ts +15 -0
  83. package/lib/typescript/module/report/index.d.ts +11 -0
  84. package/lib/typescript/module/share/index.d.ts +10 -0
  85. package/lib/typescript/module/types.d.ts +152 -0
  86. package/lib/typescript/module/validation.d.ts +10 -0
  87. package/package.json +124 -0
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImageViewer = exports.AttachmentList = void 0;
7
+ var _mobileUi = require("@webority/mobile-ui");
8
+ var _react = require("react");
9
+ var _reactNative = require("react-native");
10
+ var _reactNativeGestureHandler = require("react-native-gesture-handler");
11
+ var _format = require("../format.js");
12
+ var _SupportProvider = require("../SupportProvider.js");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ const AttachmentThumb = ({
15
+ attachment,
16
+ onOpen
17
+ }) => {
18
+ const theme = (0, _mobileUi.useTheme)();
19
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
20
+ const load = (0, _SupportProvider.useAttachmentLoader)();
21
+ const [state, setState] = (0, _react.useState)({
22
+ status: 'loading'
23
+ });
24
+ (0, _react.useEffect)(() => {
25
+ let cancelled = false;
26
+ load(attachment).then(uri => !cancelled && setState({
27
+ status: 'ready',
28
+ uri
29
+ }), () => !cancelled && setState({
30
+ status: 'error'
31
+ }));
32
+ return () => {
33
+ cancelled = true;
34
+ };
35
+ }, [load, attachment]);
36
+ if (state.status === 'loading') {
37
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
38
+ style: styles.thumb,
39
+ testID: `support-attachment-${attachment.id}`,
40
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
41
+ size: "sm",
42
+ accessibilityLabel: `Loading ${attachment.fileName}`
43
+ })
44
+ });
45
+ }
46
+ if (state.status === 'error') {
47
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
48
+ style: styles.thumb,
49
+ accessible: true,
50
+ accessibilityLabel: `${attachment.fileName} could not be loaded`,
51
+ testID: `support-attachment-${attachment.id}`,
52
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
53
+ variant: "caption",
54
+ color: "tertiary",
55
+ align: "center",
56
+ children: "Could not load"
57
+ })
58
+ });
59
+ }
60
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
61
+ onPress: () => onOpen(state.uri, attachment.fileName),
62
+ accessibilityRole: "imagebutton",
63
+ accessibilityLabel: `Open ${attachment.fileName}`,
64
+ testID: `support-attachment-${attachment.id}`,
65
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
66
+ source: {
67
+ uri: state.uri
68
+ },
69
+ style: styles.thumb,
70
+ resizeMode: "cover"
71
+ })
72
+ });
73
+ };
74
+
75
+ /**
76
+ * A file that is not an image. Tappable when the app gave SupportProvider an
77
+ * `openFile`: it is downloaded through the signed-in client, then handed to the OS.
78
+ */
79
+ const FileRow = ({
80
+ attachment
81
+ }) => {
82
+ const theme = (0, _mobileUi.useTheme)();
83
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
84
+ const open = (0, _SupportProvider.useAttachmentOpener)();
85
+ const [opening, setOpening] = (0, _react.useState)(false);
86
+ const label = `${attachment.fileName}, ${(0, _format.formatBytes)(attachment.sizeBytes)}`;
87
+ if (!open) {
88
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
89
+ variant: "caption",
90
+ color: "secondary",
91
+ testID: `support-file-${attachment.id}`,
92
+ children: label
93
+ });
94
+ }
95
+ const onPress = async () => {
96
+ setOpening(true);
97
+ try {
98
+ await open(attachment);
99
+ } catch {
100
+ _mobileUi.toast.error(`Could not open ${attachment.fileName}. Try again.`);
101
+ } finally {
102
+ setOpening(false);
103
+ }
104
+ };
105
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
106
+ onPress: () => void onPress(),
107
+ disabled: opening,
108
+ accessibilityRole: "button",
109
+ accessibilityLabel: `Open ${attachment.fileName}`,
110
+ accessibilityState: {
111
+ busy: opening,
112
+ disabled: opening
113
+ },
114
+ style: styles.fileRow,
115
+ testID: `support-file-${attachment.id}`,
116
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.AppIcon, {
117
+ name: "document",
118
+ size: 16,
119
+ color: theme.colors.text.link
120
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
121
+ variant: "caption",
122
+ color: "link",
123
+ children: label
124
+ }), opening && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
125
+ size: "sm",
126
+ accessibilityLabel: `Opening ${attachment.fileName}`
127
+ })]
128
+ });
129
+ };
130
+
131
+ /** A message's files: images as tappable thumbnails, anything else as a file row. */
132
+ const AttachmentList = ({
133
+ attachments,
134
+ onOpenImage
135
+ }) => {
136
+ const theme = (0, _mobileUi.useTheme)();
137
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
138
+ if (attachments.length === 0) {
139
+ return null;
140
+ }
141
+ const images = attachments.filter(a => (0, _format.isImage)(a.contentType));
142
+ const others = attachments.filter(a => !(0, _format.isImage)(a.contentType));
143
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
144
+ style: styles.list,
145
+ children: [images.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
146
+ style: styles.thumbRow,
147
+ children: images.map(a => /*#__PURE__*/(0, _jsxRuntime.jsx)(AttachmentThumb, {
148
+ attachment: a,
149
+ onOpen: onOpenImage
150
+ }, a.id))
151
+ }), others.map(a => /*#__PURE__*/(0, _jsxRuntime.jsx)(FileRow, {
152
+ attachment: a
153
+ }, a.id))]
154
+ });
155
+ };
156
+
157
+ /** Full-screen view of one image with pinch to zoom. */
158
+ exports.AttachmentList = AttachmentList;
159
+ const ImageViewer = ({
160
+ image,
161
+ onClose
162
+ }) => {
163
+ const theme = (0, _mobileUi.useTheme)();
164
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
165
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
166
+ visible: image !== null,
167
+ onRequestClose: onClose,
168
+ animationType: "fade",
169
+ transparent: false,
170
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeGestureHandler.GestureHandlerRootView, {
171
+ style: styles.viewer,
172
+ testID: "support-image-viewer",
173
+ children: [image && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.ZoomableImage, {
174
+ accessibilityLabel: image.name,
175
+ style: styles.viewerImageWrap,
176
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
177
+ source: {
178
+ uri: image.uri
179
+ },
180
+ style: styles.viewerImage,
181
+ resizeMode: "contain"
182
+ })
183
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
184
+ style: styles.viewerClose,
185
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.IconButton, {
186
+ icon: ({
187
+ color,
188
+ size
189
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.AppIcon, {
190
+ name: "close-circle",
191
+ color: color,
192
+ size: size
193
+ }),
194
+ accessibilityLabel: "Close image",
195
+ onPress: onClose,
196
+ variant: "glass"
197
+ })
198
+ })]
199
+ })
200
+ });
201
+ };
202
+ exports.ImageViewer = ImageViewer;
203
+ const THUMB = 84;
204
+ const buildStyles = theme => _reactNative.StyleSheet.create({
205
+ list: {
206
+ gap: theme.spacing.xs,
207
+ marginTop: theme.spacing.sm
208
+ },
209
+ fileRow: {
210
+ flexDirection: 'row',
211
+ alignItems: 'center',
212
+ gap: theme.spacing.xxs,
213
+ alignSelf: 'flex-start'
214
+ },
215
+ thumbRow: {
216
+ flexDirection: 'row',
217
+ flexWrap: 'wrap',
218
+ gap: theme.spacing.xs
219
+ },
220
+ thumb: {
221
+ width: THUMB,
222
+ height: THUMB,
223
+ borderRadius: theme.radius.sm,
224
+ backgroundColor: theme.colors.background.secondary,
225
+ alignItems: 'center',
226
+ justifyContent: 'center',
227
+ overflow: 'hidden'
228
+ },
229
+ viewer: {
230
+ flex: 1,
231
+ backgroundColor: theme.colors.background.inverse
232
+ },
233
+ viewerImageWrap: {
234
+ flex: 1
235
+ },
236
+ viewerImage: {
237
+ flex: 1,
238
+ width: '100%'
239
+ },
240
+ viewerClose: {
241
+ position: 'absolute',
242
+ top: theme.spacing['2xl'],
243
+ right: theme.spacing.md
244
+ }
245
+ });
246
+ //# sourceMappingURL=Attachments.js.map
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NewSupportTicket = void 0;
7
+ var _mobileUi = require("@webority/mobile-ui");
8
+ var _react = require("react");
9
+ var _reactNative = require("react-native");
10
+ var _client = require("../client.js");
11
+ var _SupportProvider = require("../SupportProvider.js");
12
+ var _validation = require("../validation.js");
13
+ var _PhotoTray = require("./PhotoTray.js");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ const TicketForm = ({
16
+ config,
17
+ styles,
18
+ onCreated,
19
+ onCancel,
20
+ initialDraft
21
+ }) => {
22
+ const createTicket = (0, _SupportProvider.useCreateSupportTicket)();
23
+ const [photos, setPhotos] = (0, _react.useState)([]);
24
+ const [screenshots, setScreenshots] = (0, _react.useState)(initialDraft?.screenshots ?? []);
25
+ const [filesError, setFilesError] = (0, _react.useState)(null);
26
+ const [submitError, setSubmitError] = (0, _react.useState)(null);
27
+ const form = (0, _mobileUi.useForm)({
28
+ initialValues: {
29
+ category: initialDraft?.category ?? '',
30
+ subject: initialDraft?.subject ?? '',
31
+ body: initialDraft?.body ?? ''
32
+ },
33
+ validate: {
34
+ category: v => (0, _validation.validateCategory)(v, config),
35
+ subject: v => (0, _validation.validateSubject)(v, config),
36
+ body: v => (0, _validation.validateBody)(v, config)
37
+ },
38
+ onSubmit: async values => {
39
+ const fileProblem = (0, _validation.validateFiles)([...screenshots, ...photos], config);
40
+ setFilesError(fileProblem ?? null);
41
+ if (fileProblem) {
42
+ return;
43
+ }
44
+ setSubmitError(null);
45
+ try {
46
+ const created = await createTicket({
47
+ category: values.category,
48
+ subject: values.subject.trim(),
49
+ body: values.body.trim(),
50
+ pageUrl: initialDraft?.pageUrl,
51
+ userAgent: initialDraft?.userAgent,
52
+ appVersion: initialDraft?.appVersion,
53
+ files: photos,
54
+ screenshots
55
+ });
56
+ onCreated(created);
57
+ } catch (e) {
58
+ setSubmitError((0, _client.toSupportApiError)(e).message);
59
+ }
60
+ }
61
+ });
62
+ const category = form.getFieldProps('category');
63
+ const subject = form.getFieldProps('subject');
64
+ const body = form.getFieldProps('body');
65
+ const attached = screenshots.length + photos.length;
66
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
67
+ contentContainerStyle: styles.content,
68
+ keyboardShouldPersistTaps: "handled",
69
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
70
+ variant: "bodySmall",
71
+ color: "secondary",
72
+ children: "Tell us what you need. We reply by email and in this ticket."
73
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Select, {
74
+ label: "Category",
75
+ placeholder: "Choose a category",
76
+ required: true,
77
+ options: config.categories.map(c => ({
78
+ label: c,
79
+ value: c
80
+ })),
81
+ value: category.value || undefined,
82
+ onChange: v => category.setValue(String(v), true),
83
+ error: category.error,
84
+ testID: "support-new-category"
85
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Input, {
86
+ label: "Subject",
87
+ placeholder: "A short summary",
88
+ required: true,
89
+ maxLength: config.subjectMaxLength,
90
+ value: subject.value,
91
+ onChangeText: subject.onChangeText,
92
+ onBlur: subject.onBlur,
93
+ error: subject.error,
94
+ testID: "support-new-subject"
95
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Input, {
96
+ label: "Message",
97
+ placeholder: "What happened, and what did you expect?",
98
+ required: true,
99
+ multiline: true,
100
+ maxLength: config.bodyMaxLength,
101
+ value: body.value,
102
+ onChangeText: body.onChangeText,
103
+ onBlur: body.onBlur,
104
+ error: body.error,
105
+ testID: "support-new-body"
106
+ }), screenshots.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_PhotoTray.PhotoTray, {
107
+ label: "Screenshot",
108
+ photos: screenshots,
109
+ onChange: setScreenshots,
110
+ remaining: 0,
111
+ allowAdd: false,
112
+ maxBytes: config.maxAttachmentBytes,
113
+ disabled: form.isSubmitting
114
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_PhotoTray.PhotoTray, {
115
+ label: "Photos",
116
+ photos: photos,
117
+ onChange: setPhotos,
118
+ remaining: config.maxAttachmentsPerMessage - attached,
119
+ maxBytes: config.maxAttachmentBytes,
120
+ disabled: form.isSubmitting
121
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
122
+ variant: "caption",
123
+ color: "tertiary",
124
+ children: `Attach up to ${config.maxAttachmentsPerMessage} files.`
125
+ }), (filesError || submitError) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
126
+ variant: "bodySmall",
127
+ color: "error",
128
+ accessibilityLiveRegion: "polite",
129
+ children: filesError ?? submitError
130
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
131
+ style: styles.actions,
132
+ children: [onCancel && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
133
+ title: "Cancel",
134
+ variant: "ghost",
135
+ tone: "neutral",
136
+ onPress: onCancel,
137
+ disabled: form.isSubmitting
138
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
139
+ title: "Send ticket",
140
+ loading: form.isSubmitting,
141
+ disabled: form.isSubmitting,
142
+ onPress: form.submit,
143
+ testID: "support-new-submit"
144
+ })]
145
+ })]
146
+ });
147
+ };
148
+ const NewSupportTicket = props => {
149
+ const theme = (0, _mobileUi.useTheme)();
150
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
151
+ const {
152
+ config,
153
+ loading,
154
+ error,
155
+ reload
156
+ } = (0, _SupportProvider.useSupportConfig)();
157
+ const testID = props.testID ?? 'support-new-ticket';
158
+ if (!config) {
159
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
160
+ style: styles.center,
161
+ testID: testID,
162
+ children: loading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
163
+ accessibilityLabel: "Loading"
164
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
165
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
166
+ variant: "body",
167
+ color: "error",
168
+ align: "center",
169
+ children: error?.message ?? 'Support is not available right now.'
170
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
171
+ title: "Try again",
172
+ variant: "ghost",
173
+ onPress: () => void reload()
174
+ })]
175
+ })
176
+ });
177
+ }
178
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
179
+ style: styles.root,
180
+ testID: testID,
181
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TicketForm, {
182
+ ...props,
183
+ config: config,
184
+ styles: styles
185
+ })
186
+ });
187
+ };
188
+ exports.NewSupportTicket = NewSupportTicket;
189
+ const buildStyles = theme => _reactNative.StyleSheet.create({
190
+ root: {
191
+ flex: 1
192
+ },
193
+ content: {
194
+ padding: theme.spacing.md,
195
+ gap: theme.spacing.md
196
+ },
197
+ center: {
198
+ flex: 1,
199
+ alignItems: 'center',
200
+ justifyContent: 'center',
201
+ padding: theme.spacing.xl,
202
+ gap: theme.spacing.sm
203
+ },
204
+ actions: {
205
+ flexDirection: 'row',
206
+ justifyContent: 'flex-end',
207
+ gap: theme.spacing.sm
208
+ }
209
+ });
210
+ //# sourceMappingURL=NewSupportTicket.js.map
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PhotoTray = void 0;
7
+ var _mobileUi = require("@webority/mobile-ui");
8
+ var _react = require("react");
9
+ var _reactNative = require("react-native");
10
+ var _photos = require("../photos.js");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ /** Photos on a message: thumbnails with remove buttons, plus library and camera buttons. */
13
+ const PhotoTray = ({
14
+ photos,
15
+ onChange,
16
+ remaining,
17
+ maxBytes,
18
+ disabled,
19
+ label,
20
+ allowAdd = true
21
+ }) => {
22
+ const theme = (0, _mobileUi.useTheme)();
23
+ const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
24
+ const [picking, setPicking] = (0, _react.useState)(null);
25
+ const [error, setError] = (0, _react.useState)(null);
26
+ const add = async source => {
27
+ setError(null);
28
+ setPicking(source);
29
+ try {
30
+ const picked = await (0, _photos.pickPhotos)(source, {
31
+ maxCount: remaining,
32
+ maxBytes
33
+ });
34
+ if (picked.length > 0) {
35
+ onChange([...photos, ...picked]);
36
+ }
37
+ } catch (e) {
38
+ setError(e instanceof Error && e.name === 'PhotoPermissionError' ? e.message : 'That photo could not be added. Try another one.');
39
+ } finally {
40
+ setPicking(null);
41
+ }
42
+ };
43
+ const full = remaining <= 0;
44
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
45
+ style: styles.root,
46
+ children: [label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
47
+ variant: "label",
48
+ color: "secondary",
49
+ children: label
50
+ }) : null, photos.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
51
+ style: styles.row,
52
+ children: photos.map((photo, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
53
+ style: styles.item,
54
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
55
+ source: {
56
+ uri: photo.uri
57
+ },
58
+ style: styles.thumb,
59
+ accessibilityLabel: photo.name
60
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
61
+ style: styles.remove,
62
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.IconButton, {
63
+ size: "sm",
64
+ variant: "glass",
65
+ icon: ({
66
+ color,
67
+ size
68
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.AppIcon, {
69
+ name: "trash",
70
+ color: color,
71
+ size: size
72
+ }),
73
+ accessibilityLabel: `Remove ${photo.name}`,
74
+ disabled: disabled,
75
+ onPress: () => onChange(photos.filter((_, i) => i !== index))
76
+ })
77
+ })]
78
+ }, `${photo.uri}-${index}`))
79
+ }), allowAdd && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
80
+ style: styles.actions,
81
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
82
+ size: "sm",
83
+ variant: "outline",
84
+ tone: "neutral",
85
+ leftIcon: "gallery",
86
+ title: "Add photo",
87
+ accessibilityLabel: "Add a photo from your library",
88
+ loading: picking === 'library',
89
+ disabled: disabled || full || picking !== null,
90
+ onPress: () => void add('library')
91
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
92
+ size: "sm",
93
+ variant: "outline",
94
+ tone: "neutral",
95
+ leftIcon: "camera",
96
+ title: "Take photo",
97
+ accessibilityLabel: "Take a photo with your camera",
98
+ loading: picking === 'camera',
99
+ disabled: disabled || full || picking !== null,
100
+ onPress: () => void add('camera')
101
+ })]
102
+ }), error ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
103
+ variant: "caption",
104
+ color: "error",
105
+ accessibilityLiveRegion: "polite",
106
+ children: error
107
+ }) : null]
108
+ });
109
+ };
110
+ exports.PhotoTray = PhotoTray;
111
+ const THUMB = 72;
112
+ const buildStyles = theme => _reactNative.StyleSheet.create({
113
+ root: {
114
+ gap: theme.spacing.xs
115
+ },
116
+ row: {
117
+ flexDirection: 'row',
118
+ flexWrap: 'wrap',
119
+ gap: theme.spacing.xs
120
+ },
121
+ item: {
122
+ width: THUMB,
123
+ height: THUMB
124
+ },
125
+ thumb: {
126
+ width: THUMB,
127
+ height: THUMB,
128
+ borderRadius: theme.radius.sm,
129
+ backgroundColor: theme.colors.background.secondary
130
+ },
131
+ remove: {
132
+ position: 'absolute',
133
+ top: theme.spacing.xxs,
134
+ right: theme.spacing.xxs
135
+ },
136
+ actions: {
137
+ flexDirection: 'row',
138
+ gap: theme.spacing.sm
139
+ }
140
+ });
141
+ //# sourceMappingURL=PhotoTray.js.map