@oxyhq/services 22.6.1 → 22.6.3

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 (72) hide show
  1. package/lib/commonjs/ui/components/fileManagement/FileViewer.js +99 -77
  2. package/lib/commonjs/ui/components/fileManagement/FileViewer.js.map +1 -1
  3. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js +33 -40
  4. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
  5. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +92 -88
  6. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  7. package/lib/commonjs/ui/screens/FileManagementScreen.js +169 -310
  8. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  9. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js +17 -14
  10. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js.map +1 -1
  11. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +16 -20
  12. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  13. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js +29 -12
  14. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js.map +1 -1
  15. package/lib/commonjs/ui/server.js +1 -1
  16. package/lib/commonjs/ui/server.js.map +1 -1
  17. package/lib/module/ui/components/fileManagement/FileViewer.js +100 -78
  18. package/lib/module/ui/components/fileManagement/FileViewer.js.map +1 -1
  19. package/lib/module/ui/components/fileManagement/UploadPreview.js +34 -41
  20. package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
  21. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +94 -90
  22. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  23. package/lib/module/ui/screens/FileManagementScreen.js +171 -313
  24. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  25. package/lib/module/ui/screens/fileManagement/FileListSection.js +17 -14
  26. package/lib/module/ui/screens/fileManagement/FileListSection.js.map +1 -1
  27. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +17 -21
  28. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  29. package/lib/module/ui/screens/fileManagement/UploadBar.js +30 -13
  30. package/lib/module/ui/screens/fileManagement/UploadBar.js.map +1 -1
  31. package/lib/module/ui/server.js +1 -1
  32. package/lib/module/ui/server.js.map +1 -1
  33. package/lib/typescript/commonjs/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
  37. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/ui/server.d.ts +1 -1
  43. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  44. package/lib/typescript/module/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
  45. package/lib/typescript/module/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
  46. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  47. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
  48. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  49. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  50. package/lib/typescript/module/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  52. package/lib/typescript/module/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  53. package/lib/typescript/module/ui/server.d.ts +1 -1
  54. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/ui/components/fileManagement/FileViewer.tsx +82 -68
  57. package/src/ui/components/fileManagement/UploadPreview.tsx +34 -29
  58. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +14 -12
  59. package/src/ui/screens/FileManagementScreen.tsx +123 -300
  60. package/src/ui/screens/fileManagement/FileListSection.tsx +10 -10
  61. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +16 -19
  62. package/src/ui/screens/fileManagement/UploadBar.tsx +29 -14
  63. package/src/ui/server.ts +1 -1
  64. package/lib/commonjs/ui/components/fileManagement/styles.js +0 -832
  65. package/lib/commonjs/ui/components/fileManagement/styles.js.map +0 -1
  66. package/lib/module/ui/components/fileManagement/styles.js +0 -828
  67. package/lib/module/ui/components/fileManagement/styles.js.map +0 -1
  68. package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts +0 -825
  69. package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts.map +0 -1
  70. package/lib/typescript/module/ui/components/fileManagement/styles.d.ts +0 -825
  71. package/lib/typescript/module/ui/components/fileManagement/styles.d.ts.map +0 -1
  72. package/src/ui/components/fileManagement/styles.ts +0 -823
@@ -1,12 +1,11 @@
1
1
  import type React from 'react';
2
2
  import { useMemo, useState, useEffect } from 'react';
3
- import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Image } from 'react-native';
3
+ import { View, Text, TouchableOpacity, ScrollView, ActivityIndicator, Image, StyleSheet } from 'react-native';
4
4
  import { Image as ExpoImage } from 'expo-image';
5
5
  import { MaterialCommunityIcons } from '@expo/vector-icons';
6
6
  import { useTheme } from '@oxyhq/bloom/theme';
7
7
  import type { FileMetadata } from '@oxyhq/core';
8
8
  import { formatFileSize } from '../../utils/fileManagement';
9
- import { fileManagementStyles } from './styles';
10
9
  import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
11
10
 
12
11
  interface FileViewerProps {
@@ -21,6 +20,30 @@ interface FileViewerProps {
21
20
  isOwner: boolean;
22
21
  }
23
22
 
23
+ // Genuinely-inline-only styles: the full-bleed blurred background is an
24
+ // `expo-image` (no className remap), and `imageContainer` is a measurement
25
+ // wrapper whose `onLayout` never fires on web for a className'd component.
26
+ const viewerStyles = StyleSheet.create({
27
+ backgroundImage: {
28
+ position: 'absolute',
29
+ top: 0,
30
+ left: 0,
31
+ right: 0,
32
+ bottom: 0,
33
+ width: '100%',
34
+ height: '100%',
35
+ zIndex: 0,
36
+ },
37
+ imageContainer: {
38
+ alignItems: 'center',
39
+ justifyContent: 'center',
40
+ flex: 1,
41
+ marginTop: 56,
42
+ marginBottom: 8,
43
+ paddingHorizontal: 12,
44
+ },
45
+ });
46
+
24
47
  export const FileViewer: React.FC<FileViewerProps> = ({
25
48
  file,
26
49
  fileContent,
@@ -43,10 +66,6 @@ export const FileViewer: React.FC<FileViewerProps> = ({
43
66
  const isVideo = file.contentType.startsWith('video/');
44
67
  const isAudio = file.contentType.startsWith('audio/');
45
68
 
46
- const bgColor = isImage && fileContent
47
- ? 'transparent'
48
- : undefined;
49
-
50
69
  const [containerWidth, setContainerWidth] = useState<number>(0);
51
70
  const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null);
52
71
 
@@ -75,27 +94,27 @@ export const FileViewer: React.FC<FileViewerProps> = ({
75
94
  // Return default size while loading
76
95
  return { width: 400, height: 400 };
77
96
  }
78
-
97
+
79
98
  const maxWidth = containerWidth - 24; // Account for padding
80
99
  const maxHeight = 500;
81
100
  const aspectRatio = imageDimensions.width / imageDimensions.height;
82
-
101
+
83
102
  // Start with natural dimensions
84
103
  let displayWidth = imageDimensions.width;
85
104
  let displayHeight = imageDimensions.height;
86
-
105
+
87
106
  // Only scale down if exceeds max width
88
107
  if (displayWidth > maxWidth) {
89
108
  displayWidth = maxWidth;
90
109
  displayHeight = displayWidth / aspectRatio;
91
110
  }
92
-
111
+
93
112
  // Only scale down if exceeds max height
94
113
  if (displayHeight > maxHeight) {
95
114
  displayHeight = maxHeight;
96
115
  displayWidth = displayHeight * aspectRatio;
97
116
  }
98
-
117
+
99
118
  return { width: displayWidth, height: displayHeight };
100
119
  }, [imageDimensions, containerWidth]);
101
120
 
@@ -154,28 +173,30 @@ export const FileViewer: React.FC<FileViewerProps> = ({
154
173
 
155
174
  return (
156
175
  <View
157
- className={isImage && fileContent ? undefined : 'bg-bg'}
158
- style={[fileManagementStyles.fileViewerContainer, bgColor ? { backgroundColor: bgColor } : undefined]}
176
+ className={`flex-1 relative ${isImage && fileContent ? '' : 'bg-bg'}`}
159
177
  >
160
178
  {/* Blurred Background Image - only for images */}
161
179
  {isImage && fileContent && (
162
180
  <>
163
181
  <ExpoImage
164
182
  source={{ uri: fileContent }}
165
- style={fileManagementStyles.backgroundImage}
183
+ style={viewerStyles.backgroundImage}
166
184
  contentFit="cover"
167
185
  blurRadius={50}
168
186
  transition={120}
169
187
  cachePolicy="memory-disk"
170
188
  />
171
- <View style={[fileManagementStyles.backgroundOverlay, { backgroundColor: colors.overlay }]} />
189
+ <View
190
+ className="absolute top-0 left-0 right-0 bottom-0"
191
+ style={{ backgroundColor: colors.overlay, zIndex: 1 }}
192
+ />
172
193
  </>
173
194
  )}
174
195
 
175
196
  {/* Floating Back Button */}
176
197
  <TouchableOpacity
177
- className="bg-card"
178
- style={fileManagementStyles.floatingBackButton}
198
+ className="bg-card absolute top-[12px] left-[12px] w-[40px] h-[40px] rounded-full items-center justify-center"
199
+ style={{ zIndex: 10 }}
179
200
  onPress={onClose}
180
201
  >
181
202
  <MaterialCommunityIcons name="arrow-left" size={20} color={colors.text} />
@@ -183,8 +204,8 @@ export const FileViewer: React.FC<FileViewerProps> = ({
183
204
 
184
205
  {/* Floating Download Button */}
185
206
  <TouchableOpacity
186
- className="bg-card"
187
- style={fileManagementStyles.floatingDownloadButton}
207
+ className="bg-card absolute top-[12px] right-[12px] w-[40px] h-[40px] rounded-full items-center justify-center"
208
+ style={{ zIndex: 10 }}
188
209
  onPress={() => onDownload(file.id, file.filename)}
189
210
  >
190
211
  <MaterialCommunityIcons name="download" size={20} color={colors.primary} />
@@ -192,19 +213,20 @@ export const FileViewer: React.FC<FileViewerProps> = ({
192
213
 
193
214
  {/* File Content */}
194
215
  <ScrollView
195
- style={fileManagementStyles.fileViewerContent}
196
- contentContainerStyle={fileManagementStyles.fileViewerContentContainer}
216
+ className="flex-1 relative"
217
+ style={{ zIndex: 2 }}
218
+ contentContainerClassName="grow px-[12px] pt-0 pb-[8px]"
197
219
  >
198
220
  {loadingFileContent ? (
199
- <View style={fileManagementStyles.fileViewerLoading}>
221
+ <View className="flex-1 justify-center items-center">
200
222
  <ActivityIndicator size="large" color={colors.primary} />
201
- <Text className="text-text" style={fileManagementStyles.fileViewerLoadingText}>
223
+ <Text className="text-text text-[16px] mt-[16px]">
202
224
  Loading file content...
203
225
  </Text>
204
226
  </View>
205
227
  ) : isImage && fileContent ? (
206
228
  <View
207
- style={fileManagementStyles.imageContainer}
229
+ style={viewerStyles.imageContainer}
208
230
  onLayout={(e) => {
209
231
  const width = e.nativeEvent.layout.width;
210
232
  if (width > 0) {
@@ -213,13 +235,11 @@ export const FileViewer: React.FC<FileViewerProps> = ({
213
235
  }}
214
236
  >
215
237
  <View
216
- style={[
217
- fileManagementStyles.imageWrapper,
218
- {
219
- width: imageDisplaySize.width,
220
- height: imageDisplaySize.height,
221
- }
222
- ]}
238
+ className="rounded-[24px] overflow-hidden self-center"
239
+ style={{
240
+ width: imageDisplaySize.width,
241
+ height: imageDisplaySize.height,
242
+ }}
223
243
  >
224
244
  <ExpoImage
225
245
  source={{ uri: fileContent }}
@@ -238,116 +258,112 @@ export const FileViewer: React.FC<FileViewerProps> = ({
238
258
  </View>
239
259
  </View>
240
260
  ) : isText && fileContent ? (
241
- <View className="bg-card" style={fileManagementStyles.textContainer}>
242
- <ScrollView style={{ flex: 1 }} nestedScrollEnabled>
243
- <Text className="text-text" style={fileManagementStyles.textContent}>
261
+ <View className="bg-card flex-1 rounded-[18px] p-[12px] min-h-[180px]" style={{ maxHeight: '80%' }}>
262
+ <ScrollView className="flex-1" nestedScrollEnabled>
263
+ <Text className="text-text text-[14px] leading-[20px]" style={{ fontFamily: 'monospace' }}>
244
264
  {fileContent}
245
265
  </Text>
246
266
  </ScrollView>
247
267
  </View>
248
268
  ) : isPDF && fileContent ? (
249
- <View style={fileManagementStyles.unsupportedFileContainer}>
269
+ <View className="flex-1 justify-center items-center py-[32px] px-[24px]">
250
270
  <MaterialCommunityIcons
251
271
  name="file-pdf-box"
252
272
  size={64}
253
273
  color={colors.textSecondary}
254
274
  />
255
- <Text className="text-text" style={fileManagementStyles.unsupportedFileTitle}>
275
+ <Text className="text-text text-[24px] font-bold mt-[16px] mb-[8px] text-center">
256
276
  PDF Preview Not Available
257
277
  </Text>
258
- <Text className="text-text-secondary" style={fileManagementStyles.unsupportedFileDescription}>
278
+ <Text className="text-text-secondary text-[16px] text-center leading-[24px] mb-[32px]">
259
279
  PDF files cannot be previewed in this viewer.{'\n'}
260
280
  Download the file to view its contents.
261
281
  </Text>
262
282
  <TouchableOpacity
263
- className="bg-primary"
264
- style={fileManagementStyles.downloadButtonLarge}
283
+ className="bg-primary flex-row items-center px-[18px] py-[12px] rounded-full gap-[8px]"
265
284
  onPress={() => onDownload(file.id, file.filename)}
266
285
  >
267
286
  <MaterialCommunityIcons name="download" size={18} color={colors.primaryForeground} />
268
- <Text style={[fileManagementStyles.downloadButtonText, { color: colors.primaryForeground }]}>Download PDF</Text>
287
+ <Text className="text-[16px] font-semibold" style={{ color: colors.primaryForeground }}>Download PDF</Text>
269
288
  </TouchableOpacity>
270
289
  </View>
271
290
  ) : isVideo && fileContent ? (
272
- <View style={fileManagementStyles.unsupportedFileContainer}>
291
+ <View className="flex-1 justify-center items-center py-[32px] px-[24px]">
273
292
  <MaterialCommunityIcons
274
293
  name="video-outline"
275
294
  size={64}
276
295
  color={colors.textSecondary}
277
296
  />
278
- <Text className="text-text" style={fileManagementStyles.unsupportedFileTitle}>
297
+ <Text className="text-text text-[24px] font-bold mt-[16px] mb-[8px] text-center">
279
298
  Video Playback Not Available
280
299
  </Text>
281
- <Text className="text-text-secondary" style={fileManagementStyles.unsupportedFileDescription}>
300
+ <Text className="text-text-secondary text-[16px] text-center leading-[24px] mb-[32px]">
282
301
  Video playback is not supported in this viewer.{'\n'}
283
302
  Download the file to view it.
284
303
  </Text>
285
304
  <TouchableOpacity
286
- className="bg-primary"
287
- style={fileManagementStyles.downloadButtonLarge}
305
+ className="bg-primary flex-row items-center px-[18px] py-[12px] rounded-full gap-[8px]"
288
306
  onPress={() => onDownload(file.id, file.filename)}
289
307
  >
290
308
  <MaterialCommunityIcons name="download" size={18} color={colors.primaryForeground} />
291
- <Text style={[fileManagementStyles.downloadButtonText, { color: colors.primaryForeground }]}>Download Video</Text>
309
+ <Text className="text-[16px] font-semibold" style={{ color: colors.primaryForeground }}>Download Video</Text>
292
310
  </TouchableOpacity>
293
311
  </View>
294
312
  ) : isAudio && fileContent ? (
295
- <View style={fileManagementStyles.unsupportedFileContainer}>
313
+ <View className="flex-1 justify-center items-center py-[32px] px-[24px]">
296
314
  <MaterialCommunityIcons
297
315
  name="music-note-outline"
298
316
  size={64}
299
317
  color={colors.textSecondary}
300
318
  />
301
- <Text className="text-text" style={fileManagementStyles.unsupportedFileTitle}>
319
+ <Text className="text-text text-[24px] font-bold mt-[16px] mb-[8px] text-center">
302
320
  Audio Playback Not Available
303
321
  </Text>
304
- <Text className="text-text-secondary" style={fileManagementStyles.unsupportedFileDescription}>
322
+ <Text className="text-text-secondary text-[16px] text-center leading-[24px] mb-[32px]">
305
323
  Audio playback is not supported in this viewer.{'\n'}
306
324
  Download the file to listen to it.
307
325
  </Text>
308
326
  <TouchableOpacity
309
- className="bg-primary"
310
- style={fileManagementStyles.downloadButtonLarge}
327
+ className="bg-primary flex-row items-center px-[18px] py-[12px] rounded-full gap-[8px]"
311
328
  onPress={() => onDownload(file.id, file.filename)}
312
329
  >
313
330
  <MaterialCommunityIcons name="download" size={18} color={colors.primaryForeground} />
314
- <Text style={[fileManagementStyles.downloadButtonText, { color: colors.primaryForeground }]}>Download Audio</Text>
331
+ <Text className="text-[16px] font-semibold" style={{ color: colors.primaryForeground }}>Download Audio</Text>
315
332
  </TouchableOpacity>
316
333
  </View>
317
334
  ) : (
318
- <View style={fileManagementStyles.unsupportedFileContainer}>
335
+ <View className="flex-1 justify-center items-center py-[32px] px-[24px]">
319
336
  <MaterialCommunityIcons
320
337
  name="file-outline"
321
338
  size={64}
322
339
  color={colors.textSecondary}
323
340
  />
324
- <Text className="text-text" style={fileManagementStyles.unsupportedFileTitle}>
341
+ <Text className="text-text text-[24px] font-bold mt-[16px] mb-[8px] text-center">
325
342
  Preview Not Available
326
343
  </Text>
327
- <Text className="text-text-secondary" style={fileManagementStyles.unsupportedFileDescription}>
344
+ <Text className="text-text-secondary text-[16px] text-center leading-[24px] mb-[32px]">
328
345
  This file type cannot be previewed.{'\n'}
329
346
  Download the file to view its contents.
330
347
  </Text>
331
348
  <TouchableOpacity
332
- className="bg-primary"
333
- style={fileManagementStyles.downloadButtonLarge}
349
+ className="bg-primary flex-row items-center px-[18px] py-[12px] rounded-full gap-[8px]"
334
350
  onPress={() => onDownload(file.id, file.filename)}
335
351
  >
336
352
  <MaterialCommunityIcons name="download" size={18} color={colors.primaryForeground} />
337
- <Text style={[fileManagementStyles.downloadButtonText, { color: colors.primaryForeground }]}>Download File</Text>
353
+ <Text className="text-[16px] font-semibold" style={{ color: colors.primaryForeground }}>Download File</Text>
338
354
  </TouchableOpacity>
339
355
  </View>
340
356
  )}
341
357
  </ScrollView>
342
358
 
343
359
  {/* File Details Section - at bottom */}
344
- <View className="bg-card" style={fileManagementStyles.fileDetailsSection}>
345
- <View style={fileManagementStyles.fileDetailsSectionHeader}>
346
- <Text className="text-text" style={fileManagementStyles.fileDetailsSectionTitle}>
360
+ <View className="bg-card mx-[12px] mt-[8px] mb-[12px] rounded-[18px] overflow-hidden relative" style={{ zIndex: 2 }}>
361
+ <View className="flex-row items-center justify-between px-[12px] pt-[10px] pb-[6px]">
362
+ <Text className="text-text text-[18px] font-semibold flex-1">
347
363
  File Details
348
364
  </Text>
349
365
  <TouchableOpacity
350
- style={fileManagementStyles.fileDetailsSectionToggle}
366
+ className="p-[4px]"
351
367
  onPress={onToggleDetails}
352
368
  >
353
369
  <MaterialCommunityIcons
@@ -360,7 +376,7 @@ export const FileViewer: React.FC<FileViewerProps> = ({
360
376
 
361
377
  {showFileDetailsInViewer && (
362
378
  <>
363
- <View style={fileManagementStyles.fileDetailsSectionContent}>
379
+ <View className="px-[12px] pb-[8px]">
364
380
  <SettingsListGroup>
365
381
  {fileDetailItems.map((item) => (
366
382
  <SettingsListItem
@@ -375,17 +391,16 @@ export const FileViewer: React.FC<FileViewerProps> = ({
375
391
  </View>
376
392
 
377
393
  {isOwner && (
378
- <View style={fileManagementStyles.fileDetailsActions}>
394
+ <View className="flex-row gap-[12px] mt-[8px] px-[12px] pb-[10px]">
379
395
  <TouchableOpacity
380
- className="bg-destructive"
381
- style={fileManagementStyles.fileDetailsActionButton}
396
+ className="bg-destructive flex-1 flex-row items-center justify-center py-[12px] rounded-full gap-[6px]"
382
397
  onPress={() => {
383
398
  onClose();
384
399
  onDelete(file.id, file.filename);
385
400
  }}
386
401
  >
387
402
  <MaterialCommunityIcons name="delete" size={16} color={colors.negativeForeground} />
388
- <Text style={[fileManagementStyles.fileDetailsActionText, { color: colors.negativeForeground }]}>Delete</Text>
403
+ <Text className="text-[14px] font-semibold" style={{ color: colors.negativeForeground }}>Delete</Text>
389
404
  </TouchableOpacity>
390
405
  </View>
391
406
  )}
@@ -395,4 +410,3 @@ export const FileViewer: React.FC<FileViewerProps> = ({
395
410
  </View>
396
411
  );
397
412
  };
398
-
@@ -1,12 +1,11 @@
1
1
  import type React from 'react';
2
- import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
2
+ import { View, Text, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
3
3
  import { Image as ExpoImage } from 'expo-image';
4
4
  import { Ionicons } from '@expo/vector-icons';
5
5
  import { Dialog, type DialogControlProps } from '@oxyhq/bloom';
6
6
  import { useTheme } from '@oxyhq/bloom/theme';
7
7
  import type { RNFileDescriptor } from '@oxyhq/core';
8
8
  import { formatFileSize, getFileIcon } from '../../utils/fileManagement';
9
- import { fileManagementStyles } from './styles';
10
9
 
11
10
  interface PendingFile {
12
11
  file: File | Blob | RNFileDescriptor;
@@ -25,6 +24,15 @@ interface UploadPreviewProps {
25
24
  inline?: boolean;
26
25
  }
27
26
 
27
+ // `expo-image` takes no className remap — the thumbnail size stays inline.
28
+ const previewStyles = StyleSheet.create({
29
+ thumbnail: {
30
+ width: 60,
31
+ height: 60,
32
+ borderRadius: 8,
33
+ },
34
+ });
35
+
28
36
  const UploadPreviewContent: React.FC<{
29
37
  pendingFiles: PendingFile[];
30
38
  onConfirm: () => void;
@@ -38,13 +46,13 @@ const UploadPreviewContent: React.FC<{
38
46
  onRemoveFile,
39
47
  showActions = true,
40
48
  }) => {
41
- const { colors, isDark } = useTheme();
49
+ const { colors } = useTheme();
42
50
  const totalSize = pendingFiles.reduce((sum, f) => sum + f.size, 0);
43
51
 
44
52
  return (
45
- <View style={[fileManagementStyles.uploadPreviewContainer, { backgroundColor: colors.background }]}>
46
- <View className="border-b border-border" style={fileManagementStyles.uploadPreviewHeader}>
47
- <Text className="text-text" style={fileManagementStyles.uploadPreviewTitle}>
53
+ <View className="bg-bg flex-1">
54
+ <View className="border-b border-border flex-row items-center justify-between px-[16px] py-[16px]">
55
+ <Text className="text-text text-[20px] font-bold">
48
56
  Review Files ({pendingFiles.length})
49
57
  </Text>
50
58
  <TouchableOpacity onPress={onCancel}>
@@ -52,23 +60,25 @@ const UploadPreviewContent: React.FC<{
52
60
  </TouchableOpacity>
53
61
  </View>
54
62
 
55
- <ScrollView style={fileManagementStyles.uploadPreviewList}>
63
+ <ScrollView className="flex-1 p-[16px]">
56
64
  {pendingFiles.map((pendingFile, index) => {
57
65
  const isImage = pendingFile.type.startsWith('image/');
58
66
  return (
59
67
  <View
60
68
  key={index}
61
- className="bg-secondary border-border"
62
- style={fileManagementStyles.uploadPreviewItem}
69
+ className="bg-secondary border-border flex-row items-center p-[12px] rounded-[12px] border mb-[12px] gap-[12px]"
63
70
  >
64
71
  {isImage && pendingFile.preview ? (
65
72
  <ExpoImage
66
73
  source={{ uri: pendingFile.preview }}
67
- style={fileManagementStyles.uploadPreviewThumbnail}
74
+ style={previewStyles.thumbnail}
68
75
  contentFit="cover"
69
76
  />
70
77
  ) : (
71
- <View style={[fileManagementStyles.uploadPreviewIconContainer, { backgroundColor: colors.backgroundSecondary }]}>
78
+ <View
79
+ className="w-[60px] h-[60px] rounded-[8px] items-center justify-center"
80
+ style={{ backgroundColor: colors.backgroundSecondary }}
81
+ >
72
82
  <Ionicons
73
83
  name={getFileIcon(pendingFile.type)}
74
84
  size={32}
@@ -76,16 +86,16 @@ const UploadPreviewContent: React.FC<{
76
86
  />
77
87
  </View>
78
88
  )}
79
- <View style={fileManagementStyles.uploadPreviewInfo}>
80
- <Text className="text-text" style={fileManagementStyles.uploadPreviewName} numberOfLines={1}>
89
+ <View className="flex-1 min-w-0">
90
+ <Text className="text-text text-[16px] font-semibold mb-[4px]" numberOfLines={1}>
81
91
  {pendingFile.name}
82
92
  </Text>
83
- <Text className="text-text-secondary" style={fileManagementStyles.uploadPreviewMeta}>
93
+ <Text className="text-text-secondary text-[13px]">
84
94
  {formatFileSize(pendingFile.size)} • {pendingFile.type}
85
95
  </Text>
86
96
  </View>
87
97
  <TouchableOpacity
88
- style={fileManagementStyles.uploadPreviewRemove}
98
+ className="p-[4px]"
89
99
  onPress={() => onRemoveFile(index)}
90
100
  >
91
101
  <Ionicons name="close-circle" size={24} color={colors.error} />
@@ -95,36 +105,31 @@ const UploadPreviewContent: React.FC<{
95
105
  })}
96
106
  </ScrollView>
97
107
 
98
- <View className="border-t border-border" style={fileManagementStyles.uploadPreviewFooter}>
99
- <View style={fileManagementStyles.uploadPreviewStats}>
100
- <Text className="text-text" style={fileManagementStyles.uploadPreviewStatsText}>
108
+ <View className="border-t border-border p-[16px]">
109
+ <View className="flex-row justify-between mb-[16px]">
110
+ <Text className="text-text text-[15px] font-semibold">
101
111
  {pendingFiles.length} file{pendingFiles.length !== 1 ? 's' : ''}
102
112
  </Text>
103
- <Text className="text-text" style={fileManagementStyles.uploadPreviewStatsText}>
113
+ <Text className="text-text text-[15px] font-semibold">
104
114
  {formatFileSize(totalSize)}
105
115
  </Text>
106
116
  </View>
107
117
  {showActions && (
108
- <View style={fileManagementStyles.uploadPreviewActions}>
118
+ <View className="flex-row gap-[12px]">
109
119
  <TouchableOpacity
110
- className="border-border"
111
- style={[
112
- fileManagementStyles.uploadPreviewCancelButton,
113
- { backgroundColor: 'transparent' }
114
- ]}
120
+ className="bg-transparent border-border flex-1 py-[14px] rounded-[12px] border items-center justify-center"
115
121
  onPress={onCancel}
116
122
  >
117
- <Text className="text-text" style={fileManagementStyles.uploadPreviewCancelText}>
123
+ <Text className="text-text text-[16px] font-semibold">
118
124
  Cancel
119
125
  </Text>
120
126
  </TouchableOpacity>
121
127
  <TouchableOpacity
122
- className="bg-primary"
123
- style={fileManagementStyles.uploadPreviewConfirmButton}
128
+ className="bg-primary flex-[2] flex-row items-center justify-center py-[14px] rounded-[12px] gap-[8px]"
124
129
  onPress={onConfirm}
125
130
  >
126
131
  <Ionicons name="cloud-upload" size={20} color={colors.primaryForeground} />
127
- <Text style={[fileManagementStyles.uploadPreviewConfirmText, { color: colors.primaryForeground }]}>Upload</Text>
132
+ <Text className="text-[16px] font-semibold" style={{ color: colors.primaryForeground }}>Upload</Text>
128
133
  </TouchableOpacity>
129
134
  </View>
130
135
  )}
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState, useCallback } from 'react';
2
- import { View, Text, type LayoutChangeEvent } from 'react-native';
2
+ import { View, Text, useWindowDimensions, type LayoutChangeEvent } from 'react-native';
3
3
  import type { FileMetadata } from '@oxyhq/core';
4
4
 
5
5
  export interface JustifiedPhotoGridProps {
@@ -37,15 +37,22 @@ const JustifiedPhotoGrid: React.FC<JustifiedPhotoGridProps> = ({
37
37
  maxRowHeight = 300,
38
38
  dateFormatLocale = 'en-US',
39
39
  }) => {
40
- // Responsive width measurement if not explicitly provided
40
+ // Responsive width measurement if not explicitly provided. Never gate the
41
+ // whole grid on measurement — onLayout is unreliable on web (same class of
42
+ // bug fixed in PhotoPickerSection). Fall back to window width until measured.
43
+ const { width: windowWidth } = useWindowDimensions();
41
44
  const [measuredWidth, setMeasuredWidth] = useState<number | null>(null);
42
- const effectiveWidth = explicitWidth ?? measuredWidth ?? 0; // 0 until measured
45
+ const resolvedExplicitWidth =
46
+ explicitWidth != null && explicitWidth > 0 ? explicitWidth : null;
47
+ const resolvedMeasuredWidth =
48
+ measuredWidth != null && measuredWidth > 0 ? measuredWidth : null;
49
+ const effectiveWidth = resolvedExplicitWidth ?? resolvedMeasuredWidth ?? windowWidth;
43
50
 
44
51
  const onLayoutContainer = useCallback((e: LayoutChangeEvent) => {
45
- if (explicitWidth) return; // ignore if controlled
52
+ if (resolvedExplicitWidth != null) return; // ignore if controlled
46
53
  const w = e.nativeEvent.layout.width;
47
54
  setMeasuredWidth(prev => (prev === w ? prev : w));
48
- }, [explicitWidth]);
55
+ }, [resolvedExplicitWidth]);
49
56
  // Ensure dimensions are loaded for displayed photos
50
57
  useEffect(() => {
51
58
  loadPhotoDimensions(photos);
@@ -72,11 +79,8 @@ const JustifiedPhotoGrid: React.FC<JustifiedPhotoGridProps> = ({
72
79
 
73
80
  return (
74
81
  <View style={{ width: '100%' }} onLayout={onLayoutContainer}>
75
- {/* If width not yet known (uncontrolled), avoid rendering to prevent layout jump */}
76
- {effectiveWidth === 0 && !explicitWidth ? null : (
77
- <>
78
- {sortedDates.map((date: string) => {
79
- const dayPhotos = photosByDate[date];
82
+ {sortedDates.map((date: string) => {
83
+ const dayPhotos = photosByDate[date];
80
84
  // createJustifiedRows should build rows such that the "ideal" height (availableWidth / totalAspect) stays within min/max.
81
85
  // We pass the effective container width.
82
86
  const dateWidth = dateWidths[date] ?? effectiveWidth; // fallback to overall width until measured
@@ -148,8 +152,6 @@ const JustifiedPhotoGrid: React.FC<JustifiedPhotoGridProps> = ({
148
152
  </View>
149
153
  );
150
154
  })}
151
- </>
152
- )}
153
155
  </View>
154
156
  );
155
157
  };