@harkenapp/sdk-react-native 0.0.1-alpha.1 → 0.0.1-alpha.2
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 +44 -7
- package/app.plugin.cjs +12 -17
- package/dist/__mocks__/async-storage.d.ts +16 -0
- package/dist/__mocks__/async-storage.d.ts.map +1 -0
- package/dist/__mocks__/async-storage.js +39 -0
- package/dist/__mocks__/async-storage.js.map +1 -0
- package/dist/__mocks__/expo-document-picker.d.ts +26 -0
- package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-document-picker.js +25 -0
- package/dist/__mocks__/expo-document-picker.js.map +1 -0
- package/dist/__mocks__/expo-file-system.d.ts +42 -0
- package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
- package/dist/__mocks__/expo-file-system.js +37 -0
- package/dist/__mocks__/expo-file-system.js.map +1 -0
- package/dist/__mocks__/expo-image-picker.d.ts +30 -0
- package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-image-picker.js +30 -0
- package/dist/__mocks__/expo-image-picker.js.map +1 -0
- package/dist/__mocks__/expo-secure-store.d.ts +15 -0
- package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
- package/dist/__mocks__/expo-secure-store.js +30 -0
- package/dist/__mocks__/expo-secure-store.js.map +1 -0
- package/dist/__mocks__/react-native.d.ts +73 -0
- package/dist/__mocks__/react-native.d.ts.map +1 -0
- package/dist/__mocks__/react-native.js +45 -0
- package/dist/__mocks__/react-native.js.map +1 -0
- package/dist/api/client.d.ts +8 -8
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -19
- package/dist/api/client.js.map +1 -1
- package/dist/api/client.test.d.ts +2 -0
- package/dist/api/client.test.d.ts.map +1 -0
- package/dist/api/client.test.js +417 -0
- package/dist/api/client.test.js.map +1 -0
- package/dist/api/errors.d.ts +3 -3
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +3 -3
- package/dist/api/errors.js.map +1 -1
- package/dist/api/errors.test.d.ts +2 -0
- package/dist/api/errors.test.d.ts.map +1 -0
- package/dist/api/errors.test.js +155 -0
- package/dist/api/errors.test.js.map +1 -0
- package/dist/api/index.d.ts +6 -6
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/retry.d.ts +1 -1
- package/dist/api/retry.d.ts.map +1 -1
- package/dist/api/retry.js.map +1 -1
- package/dist/api/retry.test.d.ts +2 -0
- package/dist/api/retry.test.d.ts.map +1 -0
- package/dist/api/retry.test.js +193 -0
- package/dist/api/retry.test.js.map +1 -0
- package/dist/attachments/FeedbackSheet.d.ts +36 -13
- package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
- package/dist/attachments/FeedbackSheet.js +50 -30
- package/dist/attachments/FeedbackSheet.js.map +1 -1
- package/dist/attachments/index.d.ts +2 -2
- package/dist/components/AttachmentGrid.d.ts +12 -4
- package/dist/components/AttachmentGrid.d.ts.map +1 -1
- package/dist/components/AttachmentGrid.js +44 -34
- package/dist/components/AttachmentGrid.js.map +1 -1
- package/dist/components/AttachmentPicker.d.ts +3 -3
- package/dist/components/AttachmentPicker.d.ts.map +1 -1
- package/dist/components/AttachmentPicker.js +34 -36
- package/dist/components/AttachmentPicker.js.map +1 -1
- package/dist/components/AttachmentPreview.d.ts +10 -4
- package/dist/components/AttachmentPreview.d.ts.map +1 -1
- package/dist/components/AttachmentPreview.js +48 -34
- package/dist/components/AttachmentPreview.js.map +1 -1
- package/dist/components/CategorySelector.d.ts +3 -3
- package/dist/components/CategorySelector.d.ts.map +1 -1
- package/dist/components/CategorySelector.js +21 -27
- package/dist/components/CategorySelector.js.map +1 -1
- package/dist/components/FeedbackForm.d.ts +3 -3
- package/dist/components/FeedbackForm.d.ts.map +1 -1
- package/dist/components/FeedbackForm.js +7 -8
- package/dist/components/FeedbackForm.js.map +1 -1
- package/dist/components/FeedbackSheet.d.ts +34 -11
- package/dist/components/FeedbackSheet.d.ts.map +1 -1
- package/dist/components/FeedbackSheet.js +46 -28
- package/dist/components/FeedbackSheet.js.map +1 -1
- package/dist/components/ThemedButton.d.ts +16 -5
- package/dist/components/ThemedButton.d.ts.map +1 -1
- package/dist/components/ThemedButton.js +38 -29
- package/dist/components/ThemedButton.js.map +1 -1
- package/dist/components/ThemedText.d.ts +3 -3
- package/dist/components/ThemedText.d.ts.map +1 -1
- package/dist/components/ThemedText.js +1 -1
- package/dist/components/ThemedText.js.map +1 -1
- package/dist/components/ThemedTextInput.d.ts +11 -2
- package/dist/components/ThemedTextInput.d.ts.map +1 -1
- package/dist/components/ThemedTextInput.js +19 -9
- package/dist/components/ThemedTextInput.js.map +1 -1
- package/dist/components/UploadStatusOverlay.d.ts +11 -3
- package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
- package/dist/components/UploadStatusOverlay.js +59 -76
- package/dist/components/UploadStatusOverlay.js.map +1 -1
- package/dist/components/index.d.ts +18 -18
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/context/HarkenContext.d.ts +20 -15
- package/dist/context/HarkenContext.d.ts.map +1 -1
- package/dist/context/HarkenContext.js +20 -17
- package/dist/context/HarkenContext.js.map +1 -1
- package/dist/context/index.d.ts +2 -2
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -5
- package/dist/hooks/useAnonymousId.js +1 -1
- package/dist/hooks/useAnonymousId.test.d.ts +2 -0
- package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
- package/dist/hooks/useAnonymousId.test.js +154 -0
- package/dist/hooks/useAnonymousId.test.js.map +1 -0
- package/dist/hooks/useAttachmentPicker.d.ts +3 -3
- package/dist/hooks/useAttachmentPicker.js +7 -7
- package/dist/hooks/useAttachmentStatus.d.ts +1 -1
- package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
- package/dist/hooks/useAttachmentStatus.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.d.ts +2 -2
- package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
- package/dist/hooks/useAttachmentUpload.js +5 -5
- package/dist/hooks/useAttachmentUpload.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
- package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
- package/dist/hooks/useAttachmentUpload.test.js +542 -0
- package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
- package/dist/hooks/useFeedback.d.ts +4 -4
- package/dist/hooks/useFeedback.d.ts.map +1 -1
- package/dist/hooks/useFeedback.js +3 -5
- package/dist/hooks/useFeedback.js.map +1 -1
- package/dist/hooks/useFeedback.test.d.ts +2 -0
- package/dist/hooks/useFeedback.test.d.ts.map +1 -0
- package/dist/hooks/useFeedback.test.js +299 -0
- package/dist/hooks/useFeedback.test.js.map +1 -0
- package/dist/hooks/useHarkenContext.d.ts +1 -1
- package/dist/hooks/useHarkenContext.js +1 -1
- package/dist/hooks/useHarkenTheme.d.ts +27 -3
- package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
- package/dist/hooks/useHarkenTheme.js +26 -2
- package/dist/hooks/useHarkenTheme.js.map +1 -1
- package/dist/index.d.ts +28 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +3 -3
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/uploadQueueService.d.ts +2 -2
- package/dist/services/uploadQueueService.d.ts.map +1 -1
- package/dist/services/uploadQueueService.js +16 -17
- package/dist/services/uploadQueueService.js.map +1 -1
- package/dist/services/uploadQueueService.test.d.ts +2 -0
- package/dist/services/uploadQueueService.test.d.ts.map +1 -0
- package/dist/services/uploadQueueService.test.js +426 -0
- package/dist/services/uploadQueueService.test.js.map +1 -0
- package/dist/services/uploadQueueStorage.d.ts +1 -1
- package/dist/services/uploadQueueStorage.d.ts.map +1 -1
- package/dist/services/uploadQueueStorage.js +4 -4
- package/dist/services/uploadQueueStorage.js.map +1 -1
- package/dist/services/uploadQueueStorage.test.d.ts +2 -0
- package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
- package/dist/services/uploadQueueStorage.test.js +200 -0
- package/dist/services/uploadQueueStorage.test.js.map +1 -0
- package/dist/storage/IdentityStore.d.ts +1 -1
- package/dist/storage/IdentityStore.d.ts.map +1 -1
- package/dist/storage/IdentityStore.js.map +1 -1
- package/dist/storage/IdentityStore.test.d.ts +2 -0
- package/dist/storage/IdentityStore.test.d.ts.map +1 -0
- package/dist/storage/IdentityStore.test.js +176 -0
- package/dist/storage/IdentityStore.test.js.map +1 -0
- package/dist/storage/SecureStoreAdapter.d.ts +1 -1
- package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
- package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
- package/dist/storage/SecureStoreAdapter.test.js +114 -0
- package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
- package/dist/storage/defaultStorage.d.ts +1 -1
- package/dist/storage/defaultStorage.js +4 -4
- package/dist/storage/defaultStorage.test.d.ts +2 -0
- package/dist/storage/defaultStorage.test.d.ts.map +1 -0
- package/dist/storage/defaultStorage.test.js +159 -0
- package/dist/storage/defaultStorage.test.js.map +1 -0
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/types.js +1 -1
- package/dist/theme/defaults.d.ts +14 -3
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/defaults.js +58 -43
- package/dist/theme/defaults.js.map +1 -1
- package/dist/theme/index.d.ts +3 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +4 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/resolver.d.ts +16 -0
- package/dist/theme/resolver.d.ts.map +1 -0
- package/dist/theme/resolver.js +375 -0
- package/dist/theme/resolver.js.map +1 -0
- package/dist/theme/resolver.test.d.ts +2 -0
- package/dist/theme/resolver.test.d.ts.map +1 -0
- package/dist/theme/resolver.test.js +344 -0
- package/dist/theme/resolver.test.js.map +1 -0
- package/dist/theme/types.d.ts +378 -5
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/uuid.d.ts.map +1 -1
- package/dist/utils/uuid.js +4 -5
- package/dist/utils/uuid.js.map +1 -1
- package/dist/utils/uuid.test.d.ts +2 -0
- package/dist/utils/uuid.test.d.ts.map +1 -0
- package/dist/utils/uuid.test.js +78 -0
- package/dist/utils/uuid.test.js.map +1 -0
- package/package.json +21 -13
- package/src/@types/expo-file-system-legacy.d.ts +3 -3
- package/src/__mocks__/async-storage.ts +46 -0
- package/src/__mocks__/expo-document-picker.ts +41 -0
- package/src/__mocks__/expo-file-system.ts +62 -0
- package/src/__mocks__/expo-image-picker.ts +48 -0
- package/src/__mocks__/expo-secure-store.ts +29 -0
- package/src/__mocks__/react-native.ts +46 -0
- package/src/api/client.test.ts +515 -0
- package/src/api/client.ts +45 -64
- package/src/api/errors.test.ts +193 -0
- package/src/api/errors.ts +7 -11
- package/src/api/index.ts +6 -10
- package/src/api/retry.test.ts +251 -0
- package/src/api/retry.ts +3 -6
- package/src/attachments/FeedbackSheet.tsx +100 -80
- package/src/attachments/index.ts +2 -2
- package/src/components/AttachmentGrid.tsx +54 -45
- package/src/components/AttachmentPicker.tsx +43 -54
- package/src/components/AttachmentPreview.tsx +51 -47
- package/src/components/CategorySelector.tsx +29 -35
- package/src/components/FeedbackForm.tsx +23 -35
- package/src/components/FeedbackSheet.tsx +89 -68
- package/src/components/ThemedButton.tsx +49 -47
- package/src/components/ThemedText.tsx +7 -10
- package/src/components/ThemedTextInput.tsx +23 -13
- package/src/components/UploadStatusOverlay.tsx +66 -89
- package/src/components/index.ts +18 -21
- package/src/context/HarkenContext.tsx +29 -28
- package/src/context/index.ts +2 -2
- package/src/domain/index.ts +2 -5
- package/src/domain/upload-queue.ts +5 -5
- package/src/hooks/index.ts +5 -5
- package/src/hooks/useAnonymousId.test.ts +189 -0
- package/src/hooks/useAnonymousId.ts +3 -3
- package/src/hooks/useAttachmentPicker.ts +12 -12
- package/src/hooks/useAttachmentStatus.ts +12 -16
- package/src/hooks/useAttachmentUpload.test.ts +632 -0
- package/src/hooks/useAttachmentUpload.ts +45 -54
- package/src/hooks/useFeedback.test.ts +376 -0
- package/src/hooks/useFeedback.ts +12 -14
- package/src/hooks/useHarkenContext.ts +4 -4
- package/src/hooks/useHarkenTheme.ts +30 -6
- package/src/index.ts +28 -52
- package/src/services/index.ts +3 -9
- package/src/services/uploadQueueService.test.ts +489 -0
- package/src/services/uploadQueueService.ts +40 -56
- package/src/services/uploadQueueStorage.test.ts +243 -0
- package/src/services/uploadQueueStorage.ts +7 -9
- package/src/storage/IdentityStore.test.ts +173 -0
- package/src/storage/IdentityStore.ts +4 -5
- package/src/storage/SecureStoreAdapter.test.ts +147 -0
- package/src/storage/SecureStoreAdapter.ts +1 -1
- package/src/storage/defaultStorage.test.ts +159 -0
- package/src/storage/defaultStorage.ts +6 -6
- package/src/storage/index.ts +5 -5
- package/src/storage/types.ts +1 -1
- package/src/theme/defaults.ts +75 -46
- package/src/theme/index.ts +15 -2
- package/src/theme/resolver.test.ts +411 -0
- package/src/theme/resolver.ts +446 -0
- package/src/theme/types.ts +453 -15
- package/src/types/config.ts +4 -4
- package/src/types/index.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/uuid.test.ts +85 -0
- package/src/utils/uuid.ts +4 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { View, Pressable, StyleSheet } from
|
|
3
|
-
import type { ViewStyle, StyleProp, ImageStyle } from
|
|
4
|
-
import { useHarkenTheme } from
|
|
5
|
-
import { ThemedText } from
|
|
6
|
-
import { AttachmentPreview } from
|
|
7
|
-
import type { AttachmentState } from
|
|
8
|
-
import type { UploadStatusLabels } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Pressable, StyleSheet } from "react-native";
|
|
3
|
+
import type { ViewStyle, StyleProp, ImageStyle } from "react-native";
|
|
4
|
+
import { useHarkenTheme } from "../hooks";
|
|
5
|
+
import { ThemedText } from "./ThemedText";
|
|
6
|
+
import { AttachmentPreview } from "./AttachmentPreview";
|
|
7
|
+
import type { AttachmentState } from "../hooks/useAttachmentUpload";
|
|
8
|
+
import type { UploadStatusLabels } from "./UploadStatusOverlay";
|
|
9
9
|
|
|
10
10
|
export interface AttachmentGridProps {
|
|
11
11
|
/** List of attachments to display */
|
|
@@ -61,6 +61,14 @@ export interface AttachmentGridProps {
|
|
|
61
61
|
*
|
|
62
62
|
* Shows attachment previews with upload status and an optional add button.
|
|
63
63
|
*
|
|
64
|
+
* Uses the following theme tokens:
|
|
65
|
+
* - `colors.addButton*` for add button colors
|
|
66
|
+
* - `spacing.tileGap` for gap between tiles
|
|
67
|
+
* - `radii.tile` for tile border radius
|
|
68
|
+
* - `sizing.tileSize` for default tile size
|
|
69
|
+
* - `sizing.addButtonIconSize` for add icon size
|
|
70
|
+
* - `opacity.disabled` for disabled state
|
|
71
|
+
*
|
|
64
72
|
* @example
|
|
65
73
|
* ```tsx
|
|
66
74
|
* // Basic usage
|
|
@@ -106,15 +114,15 @@ export function AttachmentGrid({
|
|
|
106
114
|
onRemove,
|
|
107
115
|
onAdd,
|
|
108
116
|
maxAttachments = 10,
|
|
109
|
-
tileSize
|
|
117
|
+
tileSize,
|
|
110
118
|
gap,
|
|
111
119
|
showAddButton = true,
|
|
112
120
|
disabled = false,
|
|
113
121
|
style,
|
|
114
|
-
addButtonLabel =
|
|
115
|
-
addButtonIcon =
|
|
122
|
+
addButtonLabel = "Add",
|
|
123
|
+
addButtonIcon = "+",
|
|
116
124
|
addButtonStyle,
|
|
117
|
-
emptyText =
|
|
125
|
+
emptyText = "No attachments",
|
|
118
126
|
renderAddButton,
|
|
119
127
|
renderTile,
|
|
120
128
|
tileStyle,
|
|
@@ -124,19 +132,16 @@ export function AttachmentGrid({
|
|
|
124
132
|
renderPlaceholder,
|
|
125
133
|
}: AttachmentGridProps): React.JSX.Element {
|
|
126
134
|
const theme = useHarkenTheme();
|
|
127
|
-
const
|
|
135
|
+
const { tile, addButton } = theme.components;
|
|
136
|
+
|
|
137
|
+
const effectiveTileSize = tileSize ?? tile.size;
|
|
138
|
+
const effectiveGap = gap ?? tile.gap;
|
|
128
139
|
|
|
129
140
|
const canAddMore = attachments.length < maxAttachments;
|
|
130
141
|
const shouldShowAddButton = showAddButton && canAddMore && onAdd;
|
|
131
142
|
|
|
132
143
|
return (
|
|
133
|
-
<View
|
|
134
|
-
style={[
|
|
135
|
-
styles.container,
|
|
136
|
-
{ gap: effectiveGap },
|
|
137
|
-
style,
|
|
138
|
-
]}
|
|
139
|
-
>
|
|
144
|
+
<View style={[styles.container, { gap: effectiveGap }, style]}>
|
|
140
145
|
{attachments.map((attachment) => {
|
|
141
146
|
const handleRetry = onRetry ? () => onRetry(attachment.attachmentId) : undefined;
|
|
142
147
|
const handleRemove = onRemove ? () => onRemove(attachment.attachmentId) : undefined;
|
|
@@ -161,7 +166,7 @@ export function AttachmentGrid({
|
|
|
161
166
|
error={attachment.error}
|
|
162
167
|
onRetry={handleRetry}
|
|
163
168
|
onRemove={handleRemove}
|
|
164
|
-
size={
|
|
169
|
+
size={effectiveTileSize}
|
|
165
170
|
style={tileStyle}
|
|
166
171
|
imageStyle={tileImageStyle}
|
|
167
172
|
statusLabels={statusLabels}
|
|
@@ -171,8 +176,8 @@ export function AttachmentGrid({
|
|
|
171
176
|
);
|
|
172
177
|
})}
|
|
173
178
|
|
|
174
|
-
{shouldShowAddButton &&
|
|
175
|
-
renderAddButton ? (
|
|
179
|
+
{shouldShowAddButton &&
|
|
180
|
+
(renderAddButton ? (
|
|
176
181
|
renderAddButton(onAdd, disabled)
|
|
177
182
|
) : (
|
|
178
183
|
<Pressable
|
|
@@ -181,33 +186,39 @@ export function AttachmentGrid({
|
|
|
181
186
|
style={({ pressed }) => [
|
|
182
187
|
styles.addButton,
|
|
183
188
|
{
|
|
184
|
-
width:
|
|
185
|
-
height:
|
|
186
|
-
borderRadius:
|
|
187
|
-
backgroundColor: pressed
|
|
188
|
-
? theme.colors.border
|
|
189
|
-
: theme.colors.backgroundSecondary,
|
|
189
|
+
width: effectiveTileSize,
|
|
190
|
+
height: effectiveTileSize,
|
|
191
|
+
borderRadius: tile.radius,
|
|
192
|
+
backgroundColor: pressed ? addButton.backgroundPressed : addButton.background,
|
|
190
193
|
borderWidth: 2,
|
|
191
|
-
borderColor:
|
|
192
|
-
borderStyle:
|
|
193
|
-
opacity: disabled ?
|
|
194
|
+
borderColor: addButton.border,
|
|
195
|
+
borderStyle: "dashed",
|
|
196
|
+
opacity: disabled ? theme.opacity.disabled : 1,
|
|
194
197
|
},
|
|
195
198
|
addButtonStyle,
|
|
196
199
|
]}
|
|
197
200
|
>
|
|
198
|
-
{typeof addButtonIcon ===
|
|
199
|
-
<ThemedText
|
|
201
|
+
{typeof addButtonIcon === "string" ? (
|
|
202
|
+
<ThemedText
|
|
203
|
+
style={[
|
|
204
|
+
styles.addIcon,
|
|
205
|
+
{
|
|
206
|
+
color: addButton.icon,
|
|
207
|
+
fontSize: addButton.iconSize,
|
|
208
|
+
lineHeight: addButton.iconSize * 1.15, // Scale lineHeight with iconSize
|
|
209
|
+
},
|
|
210
|
+
]}
|
|
211
|
+
>
|
|
200
212
|
{addButtonIcon}
|
|
201
213
|
</ThemedText>
|
|
202
214
|
) : (
|
|
203
215
|
addButtonIcon
|
|
204
216
|
)}
|
|
205
|
-
<ThemedText variant="caption"
|
|
217
|
+
<ThemedText variant="caption" color={addButton.text}>
|
|
206
218
|
{addButtonLabel}
|
|
207
219
|
</ThemedText>
|
|
208
220
|
</Pressable>
|
|
209
|
-
)
|
|
210
|
-
)}
|
|
221
|
+
))}
|
|
211
222
|
|
|
212
223
|
{attachments.length === 0 && !shouldShowAddButton && (
|
|
213
224
|
<View
|
|
@@ -229,19 +240,17 @@ export function AttachmentGrid({
|
|
|
229
240
|
|
|
230
241
|
const styles = StyleSheet.create({
|
|
231
242
|
container: {
|
|
232
|
-
flexDirection:
|
|
233
|
-
flexWrap:
|
|
243
|
+
flexDirection: "row",
|
|
244
|
+
flexWrap: "wrap",
|
|
234
245
|
},
|
|
235
246
|
addButton: {
|
|
236
|
-
alignItems:
|
|
237
|
-
justifyContent:
|
|
247
|
+
alignItems: "center",
|
|
248
|
+
justifyContent: "center",
|
|
238
249
|
},
|
|
239
250
|
addIcon: {
|
|
240
|
-
|
|
241
|
-
fontWeight: '300',
|
|
242
|
-
lineHeight: 32,
|
|
251
|
+
fontWeight: "300",
|
|
243
252
|
},
|
|
244
253
|
emptyState: {
|
|
245
|
-
alignItems:
|
|
254
|
+
alignItems: "center",
|
|
246
255
|
},
|
|
247
256
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Modal,
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
Platform,
|
|
8
8
|
ActionSheetIOS,
|
|
9
9
|
StyleSheet,
|
|
10
|
-
} from
|
|
11
|
-
import type { ViewStyle, StyleProp } from
|
|
12
|
-
import { SafeAreaView } from
|
|
13
|
-
import { useHarkenTheme } from
|
|
14
|
-
import { ThemedText } from
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import type { ViewStyle, StyleProp } from "react-native";
|
|
12
|
+
import { SafeAreaView } from "react-native-safe-area-context";
|
|
13
|
+
import { useHarkenTheme } from "../hooks";
|
|
14
|
+
import { ThemedText } from "./ThemedText";
|
|
15
15
|
|
|
16
|
-
export type AttachmentSource =
|
|
16
|
+
export type AttachmentSource = "camera" | "library" | "document";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Configuration for a single picker option.
|
|
@@ -126,17 +126,18 @@ export function AttachmentPicker({
|
|
|
126
126
|
onTakePhoto,
|
|
127
127
|
onPickFromLibrary,
|
|
128
128
|
onPickDocument,
|
|
129
|
-
title =
|
|
129
|
+
title = "Add Attachment",
|
|
130
130
|
renderIcon,
|
|
131
131
|
options: optionOverrides,
|
|
132
|
-
cancelLabel =
|
|
132
|
+
cancelLabel = "Cancel",
|
|
133
133
|
overlayColor,
|
|
134
134
|
sheetRadius,
|
|
135
135
|
sheetStyle,
|
|
136
136
|
optionStyle,
|
|
137
137
|
}: AttachmentPickerProps): React.JSX.Element | null {
|
|
138
138
|
const theme = useHarkenTheme();
|
|
139
|
-
const
|
|
139
|
+
const { picker } = theme.components;
|
|
140
|
+
const screenHeight = Dimensions.get("window").height;
|
|
140
141
|
|
|
141
142
|
// Prevent double-triggering ActionSheetIOS if callbacks change
|
|
142
143
|
const isShowingRef = useRef(false);
|
|
@@ -144,37 +145,35 @@ export function AttachmentPicker({
|
|
|
144
145
|
// Build options with defaults and overrides
|
|
145
146
|
const options: PickerOption[] = [
|
|
146
147
|
{
|
|
147
|
-
key:
|
|
148
|
-
label: optionOverrides?.camera?.label ??
|
|
149
|
-
description: optionOverrides?.camera?.description ??
|
|
148
|
+
key: "camera",
|
|
149
|
+
label: optionOverrides?.camera?.label ?? "Camera",
|
|
150
|
+
description: optionOverrides?.camera?.description ?? "Take a new photo",
|
|
150
151
|
color: optionOverrides?.camera?.color ?? theme.colors.accent1,
|
|
151
152
|
icon:
|
|
152
153
|
optionOverrides?.camera?.icon ??
|
|
153
|
-
(renderIcon ? renderIcon(
|
|
154
|
+
(renderIcon ? renderIcon("camera") : <DefaultIcon emoji="📷" />),
|
|
154
155
|
action: onTakePhoto,
|
|
155
156
|
hidden: optionOverrides?.camera?.hidden ?? false,
|
|
156
157
|
},
|
|
157
158
|
{
|
|
158
|
-
key:
|
|
159
|
-
label: optionOverrides?.library?.label ??
|
|
160
|
-
description:
|
|
161
|
-
optionOverrides?.library?.description ?? 'Choose from existing photos',
|
|
159
|
+
key: "library",
|
|
160
|
+
label: optionOverrides?.library?.label ?? "Photo Library",
|
|
161
|
+
description: optionOverrides?.library?.description ?? "Choose from existing photos",
|
|
162
162
|
color: optionOverrides?.library?.color ?? theme.colors.accent2,
|
|
163
163
|
icon:
|
|
164
164
|
optionOverrides?.library?.icon ??
|
|
165
|
-
(renderIcon ? renderIcon(
|
|
165
|
+
(renderIcon ? renderIcon("library") : <DefaultIcon emoji="🖼️" />),
|
|
166
166
|
action: onPickFromLibrary,
|
|
167
167
|
hidden: optionOverrides?.library?.hidden ?? false,
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
|
-
key:
|
|
171
|
-
label: optionOverrides?.document?.label ??
|
|
172
|
-
description:
|
|
173
|
-
optionOverrides?.document?.description ?? 'Browse documents and files',
|
|
170
|
+
key: "document",
|
|
171
|
+
label: optionOverrides?.document?.label ?? "Files",
|
|
172
|
+
description: optionOverrides?.document?.description ?? "Browse documents and files",
|
|
174
173
|
color: optionOverrides?.document?.color ?? theme.colors.accent3,
|
|
175
174
|
icon:
|
|
176
175
|
optionOverrides?.document?.icon ??
|
|
177
|
-
(renderIcon ? renderIcon(
|
|
176
|
+
(renderIcon ? renderIcon("document") : <DefaultIcon emoji="📄" />),
|
|
178
177
|
action: onPickDocument,
|
|
179
178
|
hidden: optionOverrides?.document?.hidden ?? false,
|
|
180
179
|
},
|
|
@@ -195,7 +194,7 @@ export function AttachmentPicker({
|
|
|
195
194
|
return;
|
|
196
195
|
}
|
|
197
196
|
|
|
198
|
-
if (visible && Platform.OS ===
|
|
197
|
+
if (visible && Platform.OS === "ios" && !isShowingRef.current) {
|
|
199
198
|
isShowingRef.current = true;
|
|
200
199
|
|
|
201
200
|
// Build iOS action sheet options from visible options
|
|
@@ -222,21 +221,16 @@ export function AttachmentPicker({
|
|
|
222
221
|
}, [visible, onClose, visibleOptions, title, cancelLabel]);
|
|
223
222
|
|
|
224
223
|
// iOS: Don't render modal - we use ActionSheetIOS instead
|
|
225
|
-
if (Platform.OS ===
|
|
224
|
+
if (Platform.OS === "ios") {
|
|
226
225
|
return null;
|
|
227
226
|
}
|
|
228
227
|
|
|
229
|
-
const resolvedOverlayColor = overlayColor ??
|
|
230
|
-
const resolvedSheetRadius = sheetRadius ??
|
|
228
|
+
const resolvedOverlayColor = overlayColor ?? picker.overlay;
|
|
229
|
+
const resolvedSheetRadius = sheetRadius ?? picker.radius;
|
|
231
230
|
|
|
232
231
|
// Android: Use bottom sheet modal
|
|
233
232
|
return (
|
|
234
|
-
<Modal
|
|
235
|
-
visible={visible}
|
|
236
|
-
transparent
|
|
237
|
-
animationType="slide"
|
|
238
|
-
onRequestClose={onClose}
|
|
239
|
-
>
|
|
233
|
+
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
|
|
240
234
|
<SafeAreaView style={styles.modalContainer}>
|
|
241
235
|
{/* Background overlay */}
|
|
242
236
|
<Pressable
|
|
@@ -248,7 +242,7 @@ export function AttachmentPicker({
|
|
|
248
242
|
style={[
|
|
249
243
|
styles.bottomSheet,
|
|
250
244
|
{
|
|
251
|
-
backgroundColor:
|
|
245
|
+
backgroundColor: picker.background,
|
|
252
246
|
maxHeight: screenHeight * 0.6,
|
|
253
247
|
borderTopLeftRadius: resolvedSheetRadius,
|
|
254
248
|
borderTopRightRadius: resolvedSheetRadius,
|
|
@@ -260,12 +254,7 @@ export function AttachmentPicker({
|
|
|
260
254
|
>
|
|
261
255
|
{/* Handle bar */}
|
|
262
256
|
<View style={styles.handleContainer}>
|
|
263
|
-
<View
|
|
264
|
-
style={[
|
|
265
|
-
styles.handle,
|
|
266
|
-
{ backgroundColor: theme.colors.textSecondary },
|
|
267
|
-
]}
|
|
268
|
-
/>
|
|
257
|
+
<View style={[styles.handle, { backgroundColor: picker.handle }]} />
|
|
269
258
|
</View>
|
|
270
259
|
|
|
271
260
|
{/* Title */}
|
|
@@ -284,8 +273,8 @@ export function AttachmentPicker({
|
|
|
284
273
|
styles.option,
|
|
285
274
|
{
|
|
286
275
|
backgroundColor: pressed
|
|
287
|
-
?
|
|
288
|
-
:
|
|
276
|
+
? picker.optionBackgroundPressed
|
|
277
|
+
: picker.optionBackground,
|
|
289
278
|
borderRadius: theme.radii.md,
|
|
290
279
|
},
|
|
291
280
|
optionStyle,
|
|
@@ -296,6 +285,8 @@ export function AttachmentPicker({
|
|
|
296
285
|
style={[
|
|
297
286
|
styles.iconContainer,
|
|
298
287
|
{
|
|
288
|
+
width: picker.iconSize,
|
|
289
|
+
height: picker.iconSize,
|
|
299
290
|
backgroundColor: option.color,
|
|
300
291
|
borderRadius: theme.radii.full,
|
|
301
292
|
},
|
|
@@ -314,7 +305,7 @@ export function AttachmentPicker({
|
|
|
314
305
|
|
|
315
306
|
{/* Cancel Button */}
|
|
316
307
|
<Pressable style={styles.cancelButton} onPress={onClose}>
|
|
317
|
-
<ThemedText
|
|
308
|
+
<ThemedText color={picker.cancelText}>{cancelLabel}</ThemedText>
|
|
318
309
|
</Pressable>
|
|
319
310
|
</View>
|
|
320
311
|
</View>
|
|
@@ -337,13 +328,13 @@ const styles = StyleSheet.create({
|
|
|
337
328
|
},
|
|
338
329
|
overlay: {
|
|
339
330
|
flex: 1,
|
|
340
|
-
justifyContent:
|
|
331
|
+
justifyContent: "flex-end",
|
|
341
332
|
},
|
|
342
333
|
bottomSheet: {
|
|
343
334
|
paddingBottom: 20,
|
|
344
335
|
},
|
|
345
336
|
handleContainer: {
|
|
346
|
-
alignItems:
|
|
337
|
+
alignItems: "center",
|
|
347
338
|
paddingVertical: 12,
|
|
348
339
|
},
|
|
349
340
|
handle: {
|
|
@@ -357,23 +348,21 @@ const styles = StyleSheet.create({
|
|
|
357
348
|
paddingBottom: 16,
|
|
358
349
|
},
|
|
359
350
|
title: {
|
|
360
|
-
textAlign:
|
|
351
|
+
textAlign: "center",
|
|
361
352
|
},
|
|
362
353
|
optionsContainer: {
|
|
363
354
|
paddingHorizontal: 20,
|
|
364
355
|
},
|
|
365
356
|
option: {
|
|
366
|
-
flexDirection:
|
|
367
|
-
alignItems:
|
|
357
|
+
flexDirection: "row",
|
|
358
|
+
alignItems: "center",
|
|
368
359
|
paddingVertical: 16,
|
|
369
360
|
paddingHorizontal: 16,
|
|
370
361
|
marginBottom: 8,
|
|
371
362
|
},
|
|
372
363
|
iconContainer: {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
alignItems: 'center',
|
|
376
|
-
justifyContent: 'center',
|
|
364
|
+
alignItems: "center",
|
|
365
|
+
justifyContent: "center",
|
|
377
366
|
marginRight: 16,
|
|
378
367
|
},
|
|
379
368
|
defaultIcon: {
|
|
@@ -385,7 +374,7 @@ const styles = StyleSheet.create({
|
|
|
385
374
|
},
|
|
386
375
|
cancelButton: {
|
|
387
376
|
paddingVertical: 16,
|
|
388
|
-
alignItems:
|
|
377
|
+
alignItems: "center",
|
|
389
378
|
marginTop: 12,
|
|
390
379
|
},
|
|
391
380
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { View, Image, StyleSheet } from
|
|
3
|
-
import type { ViewStyle, StyleProp, ImageStyle } from
|
|
4
|
-
import { useHarkenTheme } from
|
|
5
|
-
import { ThemedText } from
|
|
6
|
-
import { UploadStatusOverlay } from
|
|
7
|
-
import type { UploadStatusLabels } from
|
|
8
|
-
import { UploadPhase } from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Image, StyleSheet } from "react-native";
|
|
3
|
+
import type { ViewStyle, StyleProp, ImageStyle } from "react-native";
|
|
4
|
+
import { useHarkenTheme } from "../hooks";
|
|
5
|
+
import { ThemedText } from "./ThemedText";
|
|
6
|
+
import { UploadStatusOverlay } from "./UploadStatusOverlay";
|
|
7
|
+
import type { UploadStatusLabels } from "./UploadStatusOverlay";
|
|
8
|
+
import type { UploadPhase } from "../domain";
|
|
9
9
|
|
|
10
10
|
export interface AttachmentPreviewProps {
|
|
11
11
|
/** Local file URI for preview */
|
|
@@ -44,6 +44,12 @@ export interface AttachmentPreviewProps {
|
|
|
44
44
|
* Shows image thumbnail for images, file icon for other types.
|
|
45
45
|
* Includes upload status overlay with progress/retry/remove actions.
|
|
46
46
|
*
|
|
47
|
+
* Uses the following theme tokens:
|
|
48
|
+
* - `colors.tileBackground` for background
|
|
49
|
+
* - `colors.tileBorder` for border
|
|
50
|
+
* - `radii.tile` for border radius
|
|
51
|
+
* - `sizing.tileSize` for default size
|
|
52
|
+
*
|
|
47
53
|
* @example
|
|
48
54
|
* ```tsx
|
|
49
55
|
* // Basic usage
|
|
@@ -89,7 +95,7 @@ export function AttachmentPreview({
|
|
|
89
95
|
error,
|
|
90
96
|
onRetry,
|
|
91
97
|
onRemove,
|
|
92
|
-
size
|
|
98
|
+
size,
|
|
93
99
|
style,
|
|
94
100
|
imageStyle,
|
|
95
101
|
renderPlaceholder,
|
|
@@ -97,7 +103,10 @@ export function AttachmentPreview({
|
|
|
97
103
|
statusLabels,
|
|
98
104
|
}: AttachmentPreviewProps): React.JSX.Element {
|
|
99
105
|
const theme = useHarkenTheme();
|
|
100
|
-
const
|
|
106
|
+
const { tile } = theme.components;
|
|
107
|
+
const isImage = mimeType?.startsWith("image/") ?? true;
|
|
108
|
+
|
|
109
|
+
const effectiveSize = size ?? tile.size;
|
|
101
110
|
|
|
102
111
|
const renderFileContent = () => {
|
|
103
112
|
if (renderPlaceholder && mimeType) {
|
|
@@ -105,23 +114,14 @@ export function AttachmentPreview({
|
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
const icon = customGetFileIcon
|
|
108
|
-
? customGetFileIcon(mimeType ??
|
|
117
|
+
? customGetFileIcon(mimeType ?? "")
|
|
109
118
|
: getDefaultFileIcon(mimeType);
|
|
110
119
|
|
|
111
120
|
return (
|
|
112
121
|
<View style={styles.filePreview}>
|
|
113
|
-
{typeof icon ===
|
|
114
|
-
<ThemedText style={styles.fileIcon}>{icon}</ThemedText>
|
|
115
|
-
) : (
|
|
116
|
-
icon
|
|
117
|
-
)}
|
|
122
|
+
{typeof icon === "string" ? <ThemedText style={styles.fileIcon}>{icon}</ThemedText> : icon}
|
|
118
123
|
{fileName && (
|
|
119
|
-
<ThemedText
|
|
120
|
-
variant="caption"
|
|
121
|
-
secondary
|
|
122
|
-
numberOfLines={2}
|
|
123
|
-
style={styles.fileName}
|
|
124
|
-
>
|
|
124
|
+
<ThemedText variant="caption" secondary numberOfLines={2} style={styles.fileName}>
|
|
125
125
|
{fileName}
|
|
126
126
|
</ThemedText>
|
|
127
127
|
)}
|
|
@@ -134,13 +134,13 @@ export function AttachmentPreview({
|
|
|
134
134
|
style={[
|
|
135
135
|
styles.container,
|
|
136
136
|
{
|
|
137
|
-
width:
|
|
138
|
-
height:
|
|
139
|
-
borderRadius:
|
|
140
|
-
backgroundColor:
|
|
137
|
+
width: effectiveSize,
|
|
138
|
+
height: effectiveSize,
|
|
139
|
+
borderRadius: tile.radius,
|
|
140
|
+
backgroundColor: tile.background,
|
|
141
141
|
borderWidth: 1,
|
|
142
|
-
borderColor:
|
|
143
|
-
overflow:
|
|
142
|
+
borderColor: tile.border,
|
|
143
|
+
overflow: "hidden",
|
|
144
144
|
},
|
|
145
145
|
style,
|
|
146
146
|
]}
|
|
@@ -148,7 +148,15 @@ export function AttachmentPreview({
|
|
|
148
148
|
{isImage ? (
|
|
149
149
|
<Image
|
|
150
150
|
source={{ uri }}
|
|
151
|
-
style={[
|
|
151
|
+
style={[
|
|
152
|
+
styles.image,
|
|
153
|
+
{
|
|
154
|
+
width: effectiveSize,
|
|
155
|
+
height: effectiveSize,
|
|
156
|
+
backgroundColor: tile.background,
|
|
157
|
+
},
|
|
158
|
+
imageStyle,
|
|
159
|
+
]}
|
|
152
160
|
resizeMode="cover"
|
|
153
161
|
/>
|
|
154
162
|
) : (
|
|
@@ -171,32 +179,28 @@ export function AttachmentPreview({
|
|
|
171
179
|
* Get default file icon emoji based on MIME type.
|
|
172
180
|
*/
|
|
173
181
|
function getDefaultFileIcon(mimeType?: string): string {
|
|
174
|
-
if (!mimeType) return
|
|
182
|
+
if (!mimeType) return "📄";
|
|
175
183
|
|
|
176
|
-
if (mimeType.startsWith(
|
|
177
|
-
if (mimeType.startsWith(
|
|
178
|
-
if (mimeType ===
|
|
179
|
-
if (mimeType.includes(
|
|
180
|
-
|
|
181
|
-
if (mimeType.includes(
|
|
182
|
-
if (mimeType.includes(
|
|
183
|
-
return '📽️';
|
|
184
|
-
if (mimeType.includes('zip') || mimeType.includes('archive')) return '📦';
|
|
184
|
+
if (mimeType.startsWith("image/")) return "🖼️";
|
|
185
|
+
if (mimeType.startsWith("video/")) return "🎬";
|
|
186
|
+
if (mimeType === "application/pdf") return "📕";
|
|
187
|
+
if (mimeType.includes("spreadsheet") || mimeType.includes("excel")) return "📊";
|
|
188
|
+
if (mimeType.includes("document") || mimeType.includes("word")) return "📝";
|
|
189
|
+
if (mimeType.includes("presentation") || mimeType.includes("powerpoint")) return "📽️";
|
|
190
|
+
if (mimeType.includes("zip") || mimeType.includes("archive")) return "📦";
|
|
185
191
|
|
|
186
|
-
return
|
|
192
|
+
return "📄";
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
const styles = StyleSheet.create({
|
|
190
196
|
container: {
|
|
191
|
-
position:
|
|
192
|
-
},
|
|
193
|
-
image: {
|
|
194
|
-
backgroundColor: '#f0f0f0',
|
|
197
|
+
position: "relative",
|
|
195
198
|
},
|
|
199
|
+
image: {},
|
|
196
200
|
filePreview: {
|
|
197
201
|
flex: 1,
|
|
198
|
-
alignItems:
|
|
199
|
-
justifyContent:
|
|
202
|
+
alignItems: "center",
|
|
203
|
+
justifyContent: "center",
|
|
200
204
|
padding: 8,
|
|
201
205
|
},
|
|
202
206
|
fileIcon: {
|
|
@@ -204,7 +208,7 @@ const styles = StyleSheet.create({
|
|
|
204
208
|
},
|
|
205
209
|
fileName: {
|
|
206
210
|
marginTop: 4,
|
|
207
|
-
textAlign:
|
|
211
|
+
textAlign: "center",
|
|
208
212
|
fontSize: 10,
|
|
209
213
|
},
|
|
210
214
|
});
|