@oxyhq/services 22.12.1 → 22.12.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/lib/commonjs/ui/components/FollowButton.js +5 -8
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceNavStack.js +8 -7
- package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +48 -42
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/CreateAccountScreen.js +55 -98
- package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +21 -8
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +14 -6
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +5 -8
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/navigation/surfaceNavStack.js +8 -7
- package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/module/ui/screens/ChangeAvatarScreen.js +48 -42
- package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -1
- package/lib/module/ui/screens/CreateAccountScreen.js +58 -101
- package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +21 -8
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +14 -6
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +7 -4
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +9 -5
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +3 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/fileManagement.d.ts +10 -0
- package/lib/typescript/commonjs/ui/types/fileManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +7 -4
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +9 -5
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +3 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/fileManagement.d.ts +10 -0
- package/lib/typescript/module/ui/types/fileManagement.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/components/FollowButton.tsx +5 -8
- package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +49 -17
- package/src/ui/navigation/surfaceNavStack.ts +15 -11
- package/src/ui/screens/ChangeAvatarScreen.tsx +46 -39
- package/src/ui/screens/CreateAccountScreen.tsx +123 -167
- package/src/ui/screens/FileManagementScreen.tsx +21 -8
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +16 -6
- package/src/ui/types/fileManagement.ts +10 -0
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useState, useCallback, useRef
|
|
3
|
-
import {
|
|
4
|
-
View,
|
|
5
|
-
ActivityIndicator,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
} from 'react-native';
|
|
2
|
+
import { useState, useCallback, useRef } from 'react';
|
|
3
|
+
import { View, ActivityIndicator } from 'react-native';
|
|
8
4
|
import { Ionicons } from '@expo/vector-icons';
|
|
9
5
|
import type { AccountKind, CreateAccountInput, OrganizationCategory } from '@oxyhq/core';
|
|
10
6
|
import { ORGANIZATION_CATEGORIES } from '@oxyhq/core';
|
|
@@ -15,6 +11,7 @@ import { useTheme } from '@oxyhq/bloom/theme';
|
|
|
15
11
|
import { Text } from '@oxyhq/bloom/typography';
|
|
16
12
|
import { Button } from '@oxyhq/bloom/button';
|
|
17
13
|
import { TextField, TextFieldInput } from '@oxyhq/bloom/text-field';
|
|
14
|
+
import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
|
|
18
15
|
import { useOxy } from '../context/OxyContext';
|
|
19
16
|
import { toast } from '@oxyhq/bloom';
|
|
20
17
|
|
|
@@ -172,15 +169,6 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({
|
|
|
172
169
|
checkUsername(cleaned);
|
|
173
170
|
}, [checkUsername]);
|
|
174
171
|
|
|
175
|
-
// Cleanup debounce timer
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
return () => {
|
|
178
|
-
if (debounceTimerRef.current) {
|
|
179
|
-
clearTimeout(debounceTimerRef.current);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
}, []);
|
|
183
|
-
|
|
184
172
|
const canCreate = usernameStatus === 'available' && displayName.trim().length > 0 && !isCreating;
|
|
185
173
|
|
|
186
174
|
const handleCreate = useCallback(async () => {
|
|
@@ -234,168 +222,136 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({
|
|
|
234
222
|
const title = t('accounts.create.title') || 'Create account';
|
|
235
223
|
|
|
236
224
|
return (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
225
|
+
<View className="px-screen-margin pt-space-16 pb-space-32">
|
|
226
|
+
|
|
227
|
+
{/* Account type — canonical grouped selection rows (checkmark on the chosen one) */}
|
|
228
|
+
<SettingsListGroup title={t('accounts.create.typeSection') || 'Account type'}>
|
|
229
|
+
{KIND_OPTIONS.map((option) => {
|
|
230
|
+
const selected = option.value === kind;
|
|
231
|
+
return (
|
|
232
|
+
<SettingsListItem
|
|
233
|
+
key={option.value}
|
|
234
|
+
icon={(
|
|
235
|
+
<Ionicons
|
|
236
|
+
name={option.icon}
|
|
237
|
+
size={22}
|
|
238
|
+
color={selected ? bloomTheme.colors.primary : bloomTheme.colors.icon}
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
241
|
+
title={kindLabel(t, option.value)}
|
|
242
|
+
description={kindDescription(t, option.value)}
|
|
243
|
+
onPress={() => setKind(option.value)}
|
|
244
|
+
showChevron={false}
|
|
245
|
+
rightElement={selected ? (
|
|
246
|
+
<Ionicons name="checkmark-circle" size={20} color={bloomTheme.colors.primary} />
|
|
247
|
+
) : undefined}
|
|
248
|
+
accessibilityLabel={kindLabel(t, option.value)}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
})}
|
|
252
|
+
</SettingsListGroup>
|
|
253
|
+
|
|
254
|
+
{/* Organization category — grouped selection rows, shown only for organizations */}
|
|
255
|
+
{kind === 'organization' ? (
|
|
256
|
+
<SettingsListGroup title={t('accounts.create.organizationCategory.label')}>
|
|
257
|
+
{ORGANIZATION_CATEGORY_OPTIONS.map((option) => {
|
|
258
|
+
const selected = option === organizationCategory;
|
|
259
|
+
return (
|
|
260
|
+
<SettingsListItem
|
|
261
|
+
key={option}
|
|
262
|
+
title={organizationCategoryLabel(t, option)}
|
|
263
|
+
onPress={() => setOrganizationCategory(option)}
|
|
264
|
+
showChevron={false}
|
|
265
|
+
rightElement={selected ? (
|
|
266
|
+
<Ionicons name="checkmark-circle" size={20} color={bloomTheme.colors.primary} />
|
|
267
|
+
) : undefined}
|
|
268
|
+
accessibilityLabel={organizationCategoryLabel(t, option)}
|
|
269
|
+
/>
|
|
270
|
+
);
|
|
271
|
+
})}
|
|
272
|
+
</SettingsListGroup>
|
|
273
|
+
) : null}
|
|
274
|
+
|
|
275
|
+
{/* Details — a grouped section card hosting the form fields */}
|
|
276
|
+
<SettingsListGroup title={t('accounts.create.detailsSection') || 'Details'}>
|
|
277
|
+
<View className="p-space-16 gap-space-16">
|
|
278
|
+
{/* Username */}
|
|
242
279
|
<View className="gap-space-8">
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<Text className="text-caption font-caption text-text-secondary">
|
|
273
|
-
{kindDescription(t, option.value)}
|
|
274
|
-
</Text>
|
|
275
|
-
</View>
|
|
276
|
-
{selected ? (
|
|
277
|
-
<Ionicons name="checkmark-circle" size={20} color={bloomTheme.colors.primary} />
|
|
278
|
-
) : null}
|
|
279
|
-
</TouchableOpacity>
|
|
280
|
-
);
|
|
281
|
-
})}
|
|
280
|
+
<TextField isInvalid={usernameIsInvalid}>
|
|
281
|
+
<TextFieldInput
|
|
282
|
+
floatingLabel
|
|
283
|
+
label={t('accounts.create.username.label') || 'Username'}
|
|
284
|
+
value={username}
|
|
285
|
+
onChangeText={handleUsernameChange}
|
|
286
|
+
isInvalid={usernameIsInvalid}
|
|
287
|
+
autoCapitalize="none"
|
|
288
|
+
autoCorrect={false}
|
|
289
|
+
autoComplete="off"
|
|
290
|
+
maxLength={USERNAME_MAX}
|
|
291
|
+
/>
|
|
292
|
+
</TextField>
|
|
293
|
+
{(usernameStatus === 'checking' || usernameMessage) ? (
|
|
294
|
+
<View className="flex-row items-center gap-space-4 px-space-4">
|
|
295
|
+
{usernameStatus === 'checking' ? (
|
|
296
|
+
<ActivityIndicator size="small" color={bloomTheme.colors.primary} />
|
|
297
|
+
) : usernameStatus === 'available' ? (
|
|
298
|
+
<Ionicons name="checkmark-circle" size={16} color={bloomTheme.colors.success} />
|
|
299
|
+
) : usernameIsInvalid ? (
|
|
300
|
+
<Ionicons name="alert-circle" size={16} color={bloomTheme.colors.negative} />
|
|
301
|
+
) : null}
|
|
302
|
+
{usernameMessage ? (
|
|
303
|
+
<Text className="text-caption font-caption" style={{ color: statusColor }}>
|
|
304
|
+
{usernameMessage}
|
|
305
|
+
</Text>
|
|
306
|
+
) : null}
|
|
307
|
+
</View>
|
|
308
|
+
) : null}
|
|
282
309
|
</View>
|
|
283
310
|
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
accessibilityLabel={organizationCategoryLabel(t, option)}
|
|
298
|
-
onPress={() => setOrganizationCategory(option)}
|
|
299
|
-
activeOpacity={0.7}
|
|
300
|
-
className="px-space-12 py-space-8 rounded-radius-full"
|
|
301
|
-
style={{
|
|
302
|
-
backgroundColor: selected ? bloomTheme.colors.primarySubtle : bloomTheme.colors.card,
|
|
303
|
-
borderWidth: 1,
|
|
304
|
-
borderColor: selected ? bloomTheme.colors.primary : bloomTheme.colors.border,
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
<Text
|
|
308
|
-
className="text-caption font-captionBold"
|
|
309
|
-
style={{ color: selected ? bloomTheme.colors.primary : bloomTheme.colors.text }}
|
|
310
|
-
>
|
|
311
|
-
{organizationCategoryLabel(t, option)}
|
|
312
|
-
</Text>
|
|
313
|
-
</TouchableOpacity>
|
|
314
|
-
);
|
|
315
|
-
})}
|
|
316
|
-
</View>
|
|
317
|
-
</View>
|
|
318
|
-
) : null}
|
|
319
|
-
|
|
320
|
-
{/* Form Content */}
|
|
321
|
-
<View className="gap-space-16 p-space-16 rounded-radius-20 bg-fill">
|
|
322
|
-
{/* Username */}
|
|
323
|
-
<View className="gap-space-8">
|
|
324
|
-
<TextField isInvalid={usernameIsInvalid}>
|
|
325
|
-
<TextFieldInput
|
|
326
|
-
floatingLabel
|
|
327
|
-
label={t('accounts.create.username.label') || 'Username'}
|
|
328
|
-
value={username}
|
|
329
|
-
onChangeText={handleUsernameChange}
|
|
330
|
-
isInvalid={usernameIsInvalid}
|
|
331
|
-
autoCapitalize="none"
|
|
332
|
-
autoCorrect={false}
|
|
333
|
-
autoComplete="off"
|
|
334
|
-
maxLength={USERNAME_MAX}
|
|
335
|
-
/>
|
|
336
|
-
</TextField>
|
|
337
|
-
{(usernameStatus === 'checking' || usernameMessage) ? (
|
|
338
|
-
<View className="flex-row items-center gap-space-4 px-space-4">
|
|
339
|
-
{usernameStatus === 'checking' ? (
|
|
340
|
-
<ActivityIndicator size="small" color={bloomTheme.colors.primary} />
|
|
341
|
-
) : usernameStatus === 'available' ? (
|
|
342
|
-
<Ionicons name="checkmark-circle" size={16} color={bloomTheme.colors.success} />
|
|
343
|
-
) : usernameIsInvalid ? (
|
|
344
|
-
<Ionicons name="alert-circle" size={16} color={bloomTheme.colors.negative} />
|
|
345
|
-
) : null}
|
|
346
|
-
{usernameMessage ? (
|
|
347
|
-
<Text className="text-caption font-caption" style={{ color: statusColor }}>
|
|
348
|
-
{usernameMessage}
|
|
349
|
-
</Text>
|
|
350
|
-
) : null}
|
|
351
|
-
</View>
|
|
352
|
-
) : null}
|
|
353
|
-
</View>
|
|
354
|
-
|
|
355
|
-
{/* Display Name */}
|
|
311
|
+
{/* Display Name */}
|
|
312
|
+
<TextField>
|
|
313
|
+
<TextFieldInput
|
|
314
|
+
floatingLabel
|
|
315
|
+
label={t('accounts.create.displayName.label') || 'Display name'}
|
|
316
|
+
value={displayName}
|
|
317
|
+
onChangeText={setDisplayName}
|
|
318
|
+
maxLength={DISPLAY_NAME_MAX}
|
|
319
|
+
/>
|
|
320
|
+
</TextField>
|
|
321
|
+
|
|
322
|
+
{/* Bio */}
|
|
323
|
+
<View className="gap-space-4">
|
|
356
324
|
<TextField>
|
|
357
325
|
<TextFieldInput
|
|
358
326
|
floatingLabel
|
|
359
|
-
label={t('accounts.create.
|
|
360
|
-
value={
|
|
361
|
-
onChangeText={
|
|
362
|
-
maxLength={
|
|
327
|
+
label={t('accounts.create.bio.label') || 'Bio (optional)'}
|
|
328
|
+
value={bio}
|
|
329
|
+
onChangeText={setBio}
|
|
330
|
+
maxLength={BIO_MAX}
|
|
331
|
+
multiline
|
|
332
|
+
numberOfLines={3}
|
|
333
|
+
textAlignVertical="top"
|
|
363
334
|
/>
|
|
364
335
|
</TextField>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<TextField>
|
|
369
|
-
<TextFieldInput
|
|
370
|
-
floatingLabel
|
|
371
|
-
label={t('accounts.create.bio.label') || 'Bio (optional)'}
|
|
372
|
-
value={bio}
|
|
373
|
-
onChangeText={setBio}
|
|
374
|
-
maxLength={BIO_MAX}
|
|
375
|
-
multiline
|
|
376
|
-
numberOfLines={3}
|
|
377
|
-
textAlignVertical="top"
|
|
378
|
-
/>
|
|
379
|
-
</TextField>
|
|
380
|
-
<Text className="text-caption font-caption text-text-tertiary px-space-4 text-right">
|
|
381
|
-
{bio.length}/{BIO_MAX}
|
|
382
|
-
</Text>
|
|
383
|
-
</View>
|
|
336
|
+
<Text className="text-caption font-caption text-text-tertiary px-space-4 text-right">
|
|
337
|
+
{bio.length}/{BIO_MAX}
|
|
338
|
+
</Text>
|
|
384
339
|
</View>
|
|
385
|
-
|
|
386
|
-
{/* Create Button */}
|
|
387
|
-
<Button
|
|
388
|
-
variant="primary"
|
|
389
|
-
onPress={handleCreate}
|
|
390
|
-
disabled={!canCreate}
|
|
391
|
-
loading={isCreating}
|
|
392
|
-
accessibilityLabel={title}
|
|
393
|
-
className="w-full"
|
|
394
|
-
>
|
|
395
|
-
{title}
|
|
396
|
-
</Button>
|
|
397
340
|
</View>
|
|
398
|
-
|
|
341
|
+
</SettingsListGroup>
|
|
342
|
+
|
|
343
|
+
{/* Create Button */}
|
|
344
|
+
<Button
|
|
345
|
+
variant="primary"
|
|
346
|
+
onPress={handleCreate}
|
|
347
|
+
disabled={!canCreate}
|
|
348
|
+
loading={isCreating}
|
|
349
|
+
accessibilityLabel={title}
|
|
350
|
+
className="w-full"
|
|
351
|
+
>
|
|
352
|
+
{title}
|
|
353
|
+
</Button>
|
|
354
|
+
</View>
|
|
399
355
|
);
|
|
400
356
|
};
|
|
401
357
|
|
|
@@ -88,6 +88,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
88
88
|
selectMode = false,
|
|
89
89
|
multiSelect = false,
|
|
90
90
|
onSelect,
|
|
91
|
+
onPicked,
|
|
91
92
|
onConfirmSelection,
|
|
92
93
|
initialSelectedIds = [],
|
|
93
94
|
maxSelection,
|
|
@@ -305,7 +306,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
if (!multiSelect) {
|
|
308
|
-
if (
|
|
309
|
+
if (onPicked) {
|
|
310
|
+
// Forward-navigation picker (e.g. the avatar flow): hand the file
|
|
311
|
+
// to the caller, which pushes the NEXT frame (the cropper) on top
|
|
312
|
+
// of this picker. The picker frame stays below so Back returns here
|
|
313
|
+
// to re-pick — no dismiss, no session close.
|
|
314
|
+
onPicked(file);
|
|
315
|
+
} else if (onSelect) {
|
|
309
316
|
// Legacy callback caller (inbox, WelcomeNewUser): keep the
|
|
310
317
|
// callback + afterSelect behaviour unchanged.
|
|
311
318
|
onSelect(file);
|
|
@@ -315,8 +322,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
315
322
|
onClose?.();
|
|
316
323
|
}
|
|
317
324
|
} else {
|
|
318
|
-
// Promise-based picker
|
|
319
|
-
//
|
|
325
|
+
// Promise-based picker: resolve the surface's present() promise
|
|
326
|
+
// with the picked file.
|
|
320
327
|
dismiss?.(file);
|
|
321
328
|
}
|
|
322
329
|
return;
|
|
@@ -336,7 +343,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
336
343
|
return next;
|
|
337
344
|
});
|
|
338
345
|
scrollToSelectedFile(file.id);
|
|
339
|
-
}, [multiSelect, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
|
|
346
|
+
}, [multiSelect, onPicked, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
|
|
340
347
|
|
|
341
348
|
const confirmMultiSelection = useCallback(async () => {
|
|
342
349
|
if (!selectMode || !multiSelect) return;
|
|
@@ -427,6 +434,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
427
434
|
multiSelect,
|
|
428
435
|
afterSelect,
|
|
429
436
|
onSelect,
|
|
437
|
+
onPicked,
|
|
430
438
|
goBack,
|
|
431
439
|
onClose,
|
|
432
440
|
selectedIds,
|
|
@@ -999,16 +1007,21 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
999
1007
|
const isOwner = user?.id === targetUserId;
|
|
1000
1008
|
const allowUpload = isOwner && allowUploadInSelectMode;
|
|
1001
1009
|
|
|
1002
|
-
// Cancel = the nav bar's back affordance.
|
|
1003
|
-
//
|
|
1010
|
+
// Cancel = the nav bar's back affordance. A forward-nav picker (`onPicked`,
|
|
1011
|
+
// e.g. the avatar flow) pops its own frame; legacy callback callers keep their
|
|
1012
|
+
// close/back behaviour; a promise-based picker dismisses its surface.
|
|
1004
1013
|
const pickerCancel = useCallback(() => {
|
|
1005
|
-
if (
|
|
1014
|
+
if (onPicked) {
|
|
1015
|
+
// Forward-nav picker: Cancel/back pops THIS frame, returning to the
|
|
1016
|
+
// frame that opened the picker — never closes the whole session.
|
|
1017
|
+
goBack?.();
|
|
1018
|
+
} else if (onSelect || onConfirmSelection) {
|
|
1006
1019
|
if (onClose) onClose();
|
|
1007
1020
|
else goBack?.();
|
|
1008
1021
|
} else {
|
|
1009
1022
|
dismiss?.();
|
|
1010
1023
|
}
|
|
1011
|
-
}, [onSelect, onConfirmSelection, onClose, goBack, dismiss]);
|
|
1024
|
+
}, [onPicked, onSelect, onConfirmSelection, onClose, goBack, dismiss]);
|
|
1012
1025
|
|
|
1013
1026
|
// The picker's ONE trailing CTA, declared through the Dialog header's
|
|
1014
1027
|
// `primaryAction` (a proper Bloom Button with loading/disabled) — the
|
|
@@ -58,6 +58,8 @@ export interface UseFileUploadStateParams {
|
|
|
58
58
|
multiSelect: boolean;
|
|
59
59
|
afterSelect: 'close' | 'back' | 'none';
|
|
60
60
|
onSelect?: (file: FileMetadata) => void;
|
|
61
|
+
/** Forward-navigation picker: see {@link FileManagementScreenProps.onPicked}. */
|
|
62
|
+
onPicked?: (file: FileMetadata) => void;
|
|
61
63
|
goBack?: () => void;
|
|
62
64
|
onClose?: () => void;
|
|
63
65
|
selectedIds: Set<string>;
|
|
@@ -94,6 +96,7 @@ export const useFileUploadState = ({
|
|
|
94
96
|
multiSelect,
|
|
95
97
|
afterSelect,
|
|
96
98
|
onSelect,
|
|
99
|
+
onPicked,
|
|
97
100
|
goBack,
|
|
98
101
|
onClose,
|
|
99
102
|
selectedIds,
|
|
@@ -335,12 +338,19 @@ export const useFileUploadState = ({
|
|
|
335
338
|
setTimeout(() => {
|
|
336
339
|
const fileToSelect = uploadedFiles[0];
|
|
337
340
|
if (!multiSelect && fileToSelect) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
if (onPicked) {
|
|
342
|
+
// Forward-navigation picker (avatar flow): hand the
|
|
343
|
+
// freshly-uploaded file onward (to the cropper). No
|
|
344
|
+
// dismiss/close — the picker frame stays below.
|
|
345
|
+
onPicked(fileToSelect);
|
|
346
|
+
} else {
|
|
347
|
+
// Single select mode - directly call onSelect callback
|
|
348
|
+
onSelect?.(fileToSelect);
|
|
349
|
+
if (afterSelect === 'back') {
|
|
350
|
+
goBack?.();
|
|
351
|
+
} else if (afterSelect === 'close') {
|
|
352
|
+
onClose?.();
|
|
353
|
+
}
|
|
344
354
|
}
|
|
345
355
|
} else if (multiSelect) {
|
|
346
356
|
// Multi-select mode - add all uploaded files to selection
|
|
@@ -14,6 +14,16 @@ export interface FileManagementScreenProps extends BaseScreenProps {
|
|
|
14
14
|
multiSelect?: boolean;
|
|
15
15
|
// Callback when a file is selected (single select mode)
|
|
16
16
|
onSelect?: (file: FileMetadata) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Forward-navigation picker: called with the picked file in single-select
|
|
19
|
+
* mode when the picker is one FRAME of a larger in-surface flow. Unlike
|
|
20
|
+
* {@link onSelect}, the caller is expected to NAVIGATE the surface onward
|
|
21
|
+
* (e.g. the avatar flow pushes the cropper on top of this picker) — so on
|
|
22
|
+
* select the picker neither dismisses nor closes the session, and its
|
|
23
|
+
* Cancel/back pops THIS frame (returning to the frame that opened the picker)
|
|
24
|
+
* rather than closing. Takes precedence over `onSelect`/`dismiss` when set.
|
|
25
|
+
*/
|
|
26
|
+
onPicked?: (file: FileMetadata) => void;
|
|
17
27
|
// Callback when confirm pressed in multi-select mode
|
|
18
28
|
onConfirmSelection?: OnConfirmFileSelection;
|
|
19
29
|
// Initial selected file IDs for multi-select
|