@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.
Files changed (278) hide show
  1. package/README.md +44 -7
  2. package/app.plugin.cjs +12 -17
  3. package/dist/__mocks__/async-storage.d.ts +16 -0
  4. package/dist/__mocks__/async-storage.d.ts.map +1 -0
  5. package/dist/__mocks__/async-storage.js +39 -0
  6. package/dist/__mocks__/async-storage.js.map +1 -0
  7. package/dist/__mocks__/expo-document-picker.d.ts +26 -0
  8. package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
  9. package/dist/__mocks__/expo-document-picker.js +25 -0
  10. package/dist/__mocks__/expo-document-picker.js.map +1 -0
  11. package/dist/__mocks__/expo-file-system.d.ts +42 -0
  12. package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
  13. package/dist/__mocks__/expo-file-system.js +37 -0
  14. package/dist/__mocks__/expo-file-system.js.map +1 -0
  15. package/dist/__mocks__/expo-image-picker.d.ts +30 -0
  16. package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
  17. package/dist/__mocks__/expo-image-picker.js +30 -0
  18. package/dist/__mocks__/expo-image-picker.js.map +1 -0
  19. package/dist/__mocks__/expo-secure-store.d.ts +15 -0
  20. package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
  21. package/dist/__mocks__/expo-secure-store.js +30 -0
  22. package/dist/__mocks__/expo-secure-store.js.map +1 -0
  23. package/dist/__mocks__/react-native.d.ts +73 -0
  24. package/dist/__mocks__/react-native.d.ts.map +1 -0
  25. package/dist/__mocks__/react-native.js +45 -0
  26. package/dist/__mocks__/react-native.js.map +1 -0
  27. package/dist/api/client.d.ts +8 -8
  28. package/dist/api/client.d.ts.map +1 -1
  29. package/dist/api/client.js +17 -19
  30. package/dist/api/client.js.map +1 -1
  31. package/dist/api/client.test.d.ts +2 -0
  32. package/dist/api/client.test.d.ts.map +1 -0
  33. package/dist/api/client.test.js +417 -0
  34. package/dist/api/client.test.js.map +1 -0
  35. package/dist/api/errors.d.ts +3 -3
  36. package/dist/api/errors.d.ts.map +1 -1
  37. package/dist/api/errors.js +3 -3
  38. package/dist/api/errors.js.map +1 -1
  39. package/dist/api/errors.test.d.ts +2 -0
  40. package/dist/api/errors.test.d.ts.map +1 -0
  41. package/dist/api/errors.test.js +155 -0
  42. package/dist/api/errors.test.js.map +1 -0
  43. package/dist/api/index.d.ts +6 -6
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/index.js.map +1 -1
  46. package/dist/api/retry.d.ts +1 -1
  47. package/dist/api/retry.d.ts.map +1 -1
  48. package/dist/api/retry.js.map +1 -1
  49. package/dist/api/retry.test.d.ts +2 -0
  50. package/dist/api/retry.test.d.ts.map +1 -0
  51. package/dist/api/retry.test.js +193 -0
  52. package/dist/api/retry.test.js.map +1 -0
  53. package/dist/attachments/FeedbackSheet.d.ts +36 -13
  54. package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
  55. package/dist/attachments/FeedbackSheet.js +50 -30
  56. package/dist/attachments/FeedbackSheet.js.map +1 -1
  57. package/dist/attachments/index.d.ts +2 -2
  58. package/dist/components/AttachmentGrid.d.ts +12 -4
  59. package/dist/components/AttachmentGrid.d.ts.map +1 -1
  60. package/dist/components/AttachmentGrid.js +44 -34
  61. package/dist/components/AttachmentGrid.js.map +1 -1
  62. package/dist/components/AttachmentPicker.d.ts +3 -3
  63. package/dist/components/AttachmentPicker.d.ts.map +1 -1
  64. package/dist/components/AttachmentPicker.js +34 -36
  65. package/dist/components/AttachmentPicker.js.map +1 -1
  66. package/dist/components/AttachmentPreview.d.ts +10 -4
  67. package/dist/components/AttachmentPreview.d.ts.map +1 -1
  68. package/dist/components/AttachmentPreview.js +48 -34
  69. package/dist/components/AttachmentPreview.js.map +1 -1
  70. package/dist/components/CategorySelector.d.ts +3 -3
  71. package/dist/components/CategorySelector.d.ts.map +1 -1
  72. package/dist/components/CategorySelector.js +21 -27
  73. package/dist/components/CategorySelector.js.map +1 -1
  74. package/dist/components/FeedbackForm.d.ts +3 -3
  75. package/dist/components/FeedbackForm.d.ts.map +1 -1
  76. package/dist/components/FeedbackForm.js +7 -8
  77. package/dist/components/FeedbackForm.js.map +1 -1
  78. package/dist/components/FeedbackSheet.d.ts +34 -11
  79. package/dist/components/FeedbackSheet.d.ts.map +1 -1
  80. package/dist/components/FeedbackSheet.js +46 -28
  81. package/dist/components/FeedbackSheet.js.map +1 -1
  82. package/dist/components/ThemedButton.d.ts +16 -5
  83. package/dist/components/ThemedButton.d.ts.map +1 -1
  84. package/dist/components/ThemedButton.js +38 -29
  85. package/dist/components/ThemedButton.js.map +1 -1
  86. package/dist/components/ThemedText.d.ts +3 -3
  87. package/dist/components/ThemedText.d.ts.map +1 -1
  88. package/dist/components/ThemedText.js +1 -1
  89. package/dist/components/ThemedText.js.map +1 -1
  90. package/dist/components/ThemedTextInput.d.ts +11 -2
  91. package/dist/components/ThemedTextInput.d.ts.map +1 -1
  92. package/dist/components/ThemedTextInput.js +19 -9
  93. package/dist/components/ThemedTextInput.js.map +1 -1
  94. package/dist/components/UploadStatusOverlay.d.ts +11 -3
  95. package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
  96. package/dist/components/UploadStatusOverlay.js +59 -76
  97. package/dist/components/UploadStatusOverlay.js.map +1 -1
  98. package/dist/components/index.d.ts +18 -18
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/index.js.map +1 -1
  101. package/dist/context/HarkenContext.d.ts +20 -15
  102. package/dist/context/HarkenContext.d.ts.map +1 -1
  103. package/dist/context/HarkenContext.js +20 -17
  104. package/dist/context/HarkenContext.js.map +1 -1
  105. package/dist/context/index.d.ts +2 -2
  106. package/dist/domain/index.d.ts +2 -2
  107. package/dist/domain/index.d.ts.map +1 -1
  108. package/dist/domain/index.js.map +1 -1
  109. package/dist/hooks/index.d.ts +5 -5
  110. package/dist/hooks/useAnonymousId.js +1 -1
  111. package/dist/hooks/useAnonymousId.test.d.ts +2 -0
  112. package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
  113. package/dist/hooks/useAnonymousId.test.js +154 -0
  114. package/dist/hooks/useAnonymousId.test.js.map +1 -0
  115. package/dist/hooks/useAttachmentPicker.d.ts +3 -3
  116. package/dist/hooks/useAttachmentPicker.js +7 -7
  117. package/dist/hooks/useAttachmentStatus.d.ts +1 -1
  118. package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
  119. package/dist/hooks/useAttachmentStatus.js.map +1 -1
  120. package/dist/hooks/useAttachmentUpload.d.ts +2 -2
  121. package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
  122. package/dist/hooks/useAttachmentUpload.js +5 -5
  123. package/dist/hooks/useAttachmentUpload.js.map +1 -1
  124. package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
  125. package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
  126. package/dist/hooks/useAttachmentUpload.test.js +542 -0
  127. package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
  128. package/dist/hooks/useFeedback.d.ts +4 -4
  129. package/dist/hooks/useFeedback.d.ts.map +1 -1
  130. package/dist/hooks/useFeedback.js +3 -5
  131. package/dist/hooks/useFeedback.js.map +1 -1
  132. package/dist/hooks/useFeedback.test.d.ts +2 -0
  133. package/dist/hooks/useFeedback.test.d.ts.map +1 -0
  134. package/dist/hooks/useFeedback.test.js +299 -0
  135. package/dist/hooks/useFeedback.test.js.map +1 -0
  136. package/dist/hooks/useHarkenContext.d.ts +1 -1
  137. package/dist/hooks/useHarkenContext.js +1 -1
  138. package/dist/hooks/useHarkenTheme.d.ts +27 -3
  139. package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
  140. package/dist/hooks/useHarkenTheme.js +26 -2
  141. package/dist/hooks/useHarkenTheme.js.map +1 -1
  142. package/dist/index.d.ts +28 -28
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/services/index.d.ts +3 -3
  146. package/dist/services/index.d.ts.map +1 -1
  147. package/dist/services/index.js.map +1 -1
  148. package/dist/services/uploadQueueService.d.ts +2 -2
  149. package/dist/services/uploadQueueService.d.ts.map +1 -1
  150. package/dist/services/uploadQueueService.js +16 -17
  151. package/dist/services/uploadQueueService.js.map +1 -1
  152. package/dist/services/uploadQueueService.test.d.ts +2 -0
  153. package/dist/services/uploadQueueService.test.d.ts.map +1 -0
  154. package/dist/services/uploadQueueService.test.js +426 -0
  155. package/dist/services/uploadQueueService.test.js.map +1 -0
  156. package/dist/services/uploadQueueStorage.d.ts +1 -1
  157. package/dist/services/uploadQueueStorage.d.ts.map +1 -1
  158. package/dist/services/uploadQueueStorage.js +4 -4
  159. package/dist/services/uploadQueueStorage.js.map +1 -1
  160. package/dist/services/uploadQueueStorage.test.d.ts +2 -0
  161. package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
  162. package/dist/services/uploadQueueStorage.test.js +200 -0
  163. package/dist/services/uploadQueueStorage.test.js.map +1 -0
  164. package/dist/storage/IdentityStore.d.ts +1 -1
  165. package/dist/storage/IdentityStore.d.ts.map +1 -1
  166. package/dist/storage/IdentityStore.js.map +1 -1
  167. package/dist/storage/IdentityStore.test.d.ts +2 -0
  168. package/dist/storage/IdentityStore.test.d.ts.map +1 -0
  169. package/dist/storage/IdentityStore.test.js +176 -0
  170. package/dist/storage/IdentityStore.test.js.map +1 -0
  171. package/dist/storage/SecureStoreAdapter.d.ts +1 -1
  172. package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
  173. package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
  174. package/dist/storage/SecureStoreAdapter.test.js +114 -0
  175. package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
  176. package/dist/storage/defaultStorage.d.ts +1 -1
  177. package/dist/storage/defaultStorage.js +4 -4
  178. package/dist/storage/defaultStorage.test.d.ts +2 -0
  179. package/dist/storage/defaultStorage.test.d.ts.map +1 -0
  180. package/dist/storage/defaultStorage.test.js +159 -0
  181. package/dist/storage/defaultStorage.test.js.map +1 -0
  182. package/dist/storage/index.d.ts +5 -5
  183. package/dist/storage/types.js +1 -1
  184. package/dist/theme/defaults.d.ts +14 -3
  185. package/dist/theme/defaults.d.ts.map +1 -1
  186. package/dist/theme/defaults.js +58 -43
  187. package/dist/theme/defaults.js.map +1 -1
  188. package/dist/theme/index.d.ts +3 -2
  189. package/dist/theme/index.d.ts.map +1 -1
  190. package/dist/theme/index.js +4 -1
  191. package/dist/theme/index.js.map +1 -1
  192. package/dist/theme/resolver.d.ts +16 -0
  193. package/dist/theme/resolver.d.ts.map +1 -0
  194. package/dist/theme/resolver.js +375 -0
  195. package/dist/theme/resolver.js.map +1 -0
  196. package/dist/theme/resolver.test.d.ts +2 -0
  197. package/dist/theme/resolver.test.d.ts.map +1 -0
  198. package/dist/theme/resolver.test.js +344 -0
  199. package/dist/theme/resolver.test.js.map +1 -0
  200. package/dist/theme/types.d.ts +378 -5
  201. package/dist/theme/types.d.ts.map +1 -1
  202. package/dist/types/config.d.ts +4 -4
  203. package/dist/types/index.d.ts +2 -2
  204. package/dist/utils/index.d.ts +1 -1
  205. package/dist/utils/uuid.d.ts.map +1 -1
  206. package/dist/utils/uuid.js +4 -5
  207. package/dist/utils/uuid.js.map +1 -1
  208. package/dist/utils/uuid.test.d.ts +2 -0
  209. package/dist/utils/uuid.test.d.ts.map +1 -0
  210. package/dist/utils/uuid.test.js +78 -0
  211. package/dist/utils/uuid.test.js.map +1 -0
  212. package/package.json +21 -13
  213. package/src/@types/expo-file-system-legacy.d.ts +3 -3
  214. package/src/__mocks__/async-storage.ts +46 -0
  215. package/src/__mocks__/expo-document-picker.ts +41 -0
  216. package/src/__mocks__/expo-file-system.ts +62 -0
  217. package/src/__mocks__/expo-image-picker.ts +48 -0
  218. package/src/__mocks__/expo-secure-store.ts +29 -0
  219. package/src/__mocks__/react-native.ts +46 -0
  220. package/src/api/client.test.ts +515 -0
  221. package/src/api/client.ts +45 -64
  222. package/src/api/errors.test.ts +193 -0
  223. package/src/api/errors.ts +7 -11
  224. package/src/api/index.ts +6 -10
  225. package/src/api/retry.test.ts +251 -0
  226. package/src/api/retry.ts +3 -6
  227. package/src/attachments/FeedbackSheet.tsx +100 -80
  228. package/src/attachments/index.ts +2 -2
  229. package/src/components/AttachmentGrid.tsx +54 -45
  230. package/src/components/AttachmentPicker.tsx +43 -54
  231. package/src/components/AttachmentPreview.tsx +51 -47
  232. package/src/components/CategorySelector.tsx +29 -35
  233. package/src/components/FeedbackForm.tsx +23 -35
  234. package/src/components/FeedbackSheet.tsx +89 -68
  235. package/src/components/ThemedButton.tsx +49 -47
  236. package/src/components/ThemedText.tsx +7 -10
  237. package/src/components/ThemedTextInput.tsx +23 -13
  238. package/src/components/UploadStatusOverlay.tsx +66 -89
  239. package/src/components/index.ts +18 -21
  240. package/src/context/HarkenContext.tsx +29 -28
  241. package/src/context/index.ts +2 -2
  242. package/src/domain/index.ts +2 -5
  243. package/src/domain/upload-queue.ts +5 -5
  244. package/src/hooks/index.ts +5 -5
  245. package/src/hooks/useAnonymousId.test.ts +189 -0
  246. package/src/hooks/useAnonymousId.ts +3 -3
  247. package/src/hooks/useAttachmentPicker.ts +12 -12
  248. package/src/hooks/useAttachmentStatus.ts +12 -16
  249. package/src/hooks/useAttachmentUpload.test.ts +632 -0
  250. package/src/hooks/useAttachmentUpload.ts +45 -54
  251. package/src/hooks/useFeedback.test.ts +376 -0
  252. package/src/hooks/useFeedback.ts +12 -14
  253. package/src/hooks/useHarkenContext.ts +4 -4
  254. package/src/hooks/useHarkenTheme.ts +30 -6
  255. package/src/index.ts +28 -52
  256. package/src/services/index.ts +3 -9
  257. package/src/services/uploadQueueService.test.ts +489 -0
  258. package/src/services/uploadQueueService.ts +40 -56
  259. package/src/services/uploadQueueStorage.test.ts +243 -0
  260. package/src/services/uploadQueueStorage.ts +7 -9
  261. package/src/storage/IdentityStore.test.ts +173 -0
  262. package/src/storage/IdentityStore.ts +4 -5
  263. package/src/storage/SecureStoreAdapter.test.ts +147 -0
  264. package/src/storage/SecureStoreAdapter.ts +1 -1
  265. package/src/storage/defaultStorage.test.ts +159 -0
  266. package/src/storage/defaultStorage.ts +6 -6
  267. package/src/storage/index.ts +5 -5
  268. package/src/storage/types.ts +1 -1
  269. package/src/theme/defaults.ts +75 -46
  270. package/src/theme/index.ts +15 -2
  271. package/src/theme/resolver.test.ts +411 -0
  272. package/src/theme/resolver.ts +446 -0
  273. package/src/theme/types.ts +453 -15
  274. package/src/types/config.ts +4 -4
  275. package/src/types/index.ts +2 -2
  276. package/src/utils/index.ts +1 -1
  277. package/src/utils/uuid.test.ts +85 -0
  278. package/src/utils/uuid.ts +4 -7
@@ -1,11 +1,11 @@
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';
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 = 80,
117
+ tileSize,
110
118
  gap,
111
119
  showAddButton = true,
112
120
  disabled = false,
113
121
  style,
114
- addButtonLabel = 'Add',
115
- addButtonIcon = '+',
122
+ addButtonLabel = "Add",
123
+ addButtonIcon = "+",
116
124
  addButtonStyle,
117
- emptyText = 'No attachments',
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 effectiveGap = gap ?? theme.spacing.sm;
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={tileSize}
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: tileSize,
185
- height: tileSize,
186
- borderRadius: theme.radii.md,
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: theme.colors.border,
192
- borderStyle: 'dashed',
193
- opacity: disabled ? 0.5 : 1,
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 === 'string' ? (
199
- <ThemedText style={[styles.addIcon, { color: theme.colors.textSecondary }]}>
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" secondary>
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: 'row',
233
- flexWrap: 'wrap',
243
+ flexDirection: "row",
244
+ flexWrap: "wrap",
234
245
  },
235
246
  addButton: {
236
- alignItems: 'center',
237
- justifyContent: 'center',
247
+ alignItems: "center",
248
+ justifyContent: "center",
238
249
  },
239
250
  addIcon: {
240
- fontSize: 28,
241
- fontWeight: '300',
242
- lineHeight: 32,
251
+ fontWeight: "300",
243
252
  },
244
253
  emptyState: {
245
- alignItems: 'center',
254
+ alignItems: "center",
246
255
  },
247
256
  });
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef } from 'react';
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 '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';
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 = 'camera' | 'library' | 'document';
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 = 'Add Attachment',
129
+ title = "Add Attachment",
130
130
  renderIcon,
131
131
  options: optionOverrides,
132
- cancelLabel = 'Cancel',
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 screenHeight = Dimensions.get('window').height;
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: 'camera',
148
- label: optionOverrides?.camera?.label ?? 'Camera',
149
- description: optionOverrides?.camera?.description ?? 'Take a new photo',
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('camera') : <DefaultIcon emoji="📷" />),
154
+ (renderIcon ? renderIcon("camera") : <DefaultIcon emoji="📷" />),
154
155
  action: onTakePhoto,
155
156
  hidden: optionOverrides?.camera?.hidden ?? false,
156
157
  },
157
158
  {
158
- key: 'library',
159
- label: optionOverrides?.library?.label ?? 'Photo Library',
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('library') : <DefaultIcon emoji="🖼️" />),
165
+ (renderIcon ? renderIcon("library") : <DefaultIcon emoji="🖼️" />),
166
166
  action: onPickFromLibrary,
167
167
  hidden: optionOverrides?.library?.hidden ?? false,
168
168
  },
169
169
  {
170
- key: 'document',
171
- label: optionOverrides?.document?.label ?? 'Files',
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('document') : <DefaultIcon emoji="📄" />),
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 === 'ios' && !isShowingRef.current) {
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 === 'ios') {
224
+ if (Platform.OS === "ios") {
226
225
  return null;
227
226
  }
228
227
 
229
- const resolvedOverlayColor = overlayColor ?? theme.colors.overlay;
230
- const resolvedSheetRadius = sheetRadius ?? theme.radii.xl;
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: theme.colors.background,
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
- ? theme.colors.border
288
- : theme.colors.backgroundSecondary,
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 secondary>{cancelLabel}</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: 'flex-end',
331
+ justifyContent: "flex-end",
341
332
  },
342
333
  bottomSheet: {
343
334
  paddingBottom: 20,
344
335
  },
345
336
  handleContainer: {
346
- alignItems: 'center',
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: 'center',
351
+ textAlign: "center",
361
352
  },
362
353
  optionsContainer: {
363
354
  paddingHorizontal: 20,
364
355
  },
365
356
  option: {
366
- flexDirection: 'row',
367
- alignItems: 'center',
357
+ flexDirection: "row",
358
+ alignItems: "center",
368
359
  paddingVertical: 16,
369
360
  paddingHorizontal: 16,
370
361
  marginBottom: 8,
371
362
  },
372
363
  iconContainer: {
373
- width: 44,
374
- height: 44,
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: 'center',
377
+ alignItems: "center",
389
378
  marginTop: 12,
390
379
  },
391
380
  });
@@ -1,11 +1,11 @@
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 { UploadPhase } from '../domain';
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 = 80,
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 isImage = mimeType?.startsWith('image/') ?? true;
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 === 'string' ? (
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: size,
138
- height: size,
139
- borderRadius: theme.radii.md,
140
- backgroundColor: theme.colors.backgroundSecondary,
137
+ width: effectiveSize,
138
+ height: effectiveSize,
139
+ borderRadius: tile.radius,
140
+ backgroundColor: tile.background,
141
141
  borderWidth: 1,
142
- borderColor: theme.colors.border,
143
- overflow: 'hidden',
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={[styles.image, { width: size, height: size }, imageStyle]}
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('image/')) return '🖼️';
177
- if (mimeType.startsWith('video/')) return '🎬';
178
- if (mimeType === 'application/pdf') return '📕';
179
- if (mimeType.includes('spreadsheet') || mimeType.includes('excel'))
180
- return '📊';
181
- if (mimeType.includes('document') || mimeType.includes('word')) return '📝';
182
- if (mimeType.includes('presentation') || mimeType.includes('powerpoint'))
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: 'relative',
192
- },
193
- image: {
194
- backgroundColor: '#f0f0f0',
197
+ position: "relative",
195
198
  },
199
+ image: {},
196
200
  filePreview: {
197
201
  flex: 1,
198
- alignItems: 'center',
199
- justifyContent: 'center',
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: 'center',
211
+ textAlign: "center",
208
212
  fontSize: 10,
209
213
  },
210
214
  });