@oxyhq/services 13.2.0 → 13.3.0
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/ProfileButton.js +81 -22
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +146 -31
- package/lib/commonjs/ui/components/ProfileMenu.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +82 -23
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/ProfileMenu.js +147 -32
- package/lib/module/ui/components/ProfileMenu.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/ProfileButton.tsx +95 -17
- package/src/ui/components/ProfileMenu.tsx +147 -35
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,
|
|
1
|
+
{"version":3,"file":"ProfileButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAKH,KAAK,SAAS,EACd,KAAK,SAAS,EACjB,MAAM,cAAc,CAAC;AAiDtB,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sEAAsE;IACtE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqU/C,CAAC;AAqDF,eAAe,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMenu.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ProfileMenu.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA4B/B;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAC9B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yEAAyE;IACzE,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sEAAsE;IACtE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAiSD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAsE3C,CAAC;AA0IF,eAAe,WAAW,CAAC"}
|
|
@@ -30,7 +30,11 @@ export interface ProfileButtonProps {
|
|
|
30
30
|
* Native (bottom-sheet) is unaffected — this only influences the web popover.
|
|
31
31
|
*/
|
|
32
32
|
placement?: 'up' | 'down' | 'auto';
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Extra className applied to the outer trigger. Kept for NativeWind consumers
|
|
35
|
+
* that layer utility classes on top; the component's own layout is driven by
|
|
36
|
+
* `StyleSheet` so it renders correctly with or without NativeWind.
|
|
37
|
+
*/
|
|
34
38
|
className?: string;
|
|
35
39
|
/** Extra style applied to the outer trigger. */
|
|
36
40
|
style?: StyleProp<ViewStyle>;
|
|
@@ -48,6 +52,12 @@ export interface ProfileButtonProps {
|
|
|
48
52
|
* against the measured trigger, or as a bottom sheet (native).
|
|
49
53
|
* - **Signed out**: a "Sign in" row that calls `useAuth().signIn()`.
|
|
50
54
|
*
|
|
55
|
+
* Styling uses react-native `StyleSheet` + the Bloom theme (via `useTheme`) so
|
|
56
|
+
* the layout renders identically in EVERY consumer — including apps that do not
|
|
57
|
+
* use NativeWind (e.g. the accounts app). Only the web hover animation keeps
|
|
58
|
+
* dynamic inline `style` (the CSS transition/transform values), which is what
|
|
59
|
+
* the `react-native-web-style.d.ts` augmentation exists for.
|
|
60
|
+
*
|
|
51
61
|
* All display strings resolve through `@oxyhq/core`'s
|
|
52
62
|
* `getAccountDisplayName` / `getAccountFallbackHandle` — no hand-rolled name
|
|
53
63
|
* fallbacks. Avatars pass the bare file id as `source` + `variant="thumb"`, so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,
|
|
1
|
+
{"version":3,"file":"ProfileButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAKH,KAAK,SAAS,EACd,KAAK,SAAS,EACjB,MAAM,cAAc,CAAC;AAiDtB,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sEAAsE;IACtE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqU/C,CAAC;AAqDF,eAAe,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMenu.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ProfileMenu.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/ProfileMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA4B/B;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAC9B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yEAAyE;IACzE,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,sEAAsE;IACtE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD;AAiSD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAsE3C,CAAC;AA0IF,eAAe,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { useCallback, useRef, useState } from 'react';
|
|
|
3
3
|
import {
|
|
4
4
|
View,
|
|
5
5
|
Pressable,
|
|
6
|
+
StyleSheet,
|
|
6
7
|
Platform,
|
|
7
8
|
type StyleProp,
|
|
8
9
|
type ViewStyle,
|
|
@@ -85,7 +86,11 @@ export interface ProfileButtonProps {
|
|
|
85
86
|
* Native (bottom-sheet) is unaffected — this only influences the web popover.
|
|
86
87
|
*/
|
|
87
88
|
placement?: 'up' | 'down' | 'auto';
|
|
88
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Extra className applied to the outer trigger. Kept for NativeWind consumers
|
|
91
|
+
* that layer utility classes on top; the component's own layout is driven by
|
|
92
|
+
* `StyleSheet` so it renders correctly with or without NativeWind.
|
|
93
|
+
*/
|
|
89
94
|
className?: string;
|
|
90
95
|
/** Extra style applied to the outer trigger. */
|
|
91
96
|
style?: StyleProp<ViewStyle>;
|
|
@@ -104,6 +109,12 @@ export interface ProfileButtonProps {
|
|
|
104
109
|
* against the measured trigger, or as a bottom sheet (native).
|
|
105
110
|
* - **Signed out**: a "Sign in" row that calls `useAuth().signIn()`.
|
|
106
111
|
*
|
|
112
|
+
* Styling uses react-native `StyleSheet` + the Bloom theme (via `useTheme`) so
|
|
113
|
+
* the layout renders identically in EVERY consumer — including apps that do not
|
|
114
|
+
* use NativeWind (e.g. the accounts app). Only the web hover animation keeps
|
|
115
|
+
* dynamic inline `style` (the CSS transition/transform values), which is what
|
|
116
|
+
* the `react-native-web-style.d.ts` augmentation exists for.
|
|
117
|
+
*
|
|
107
118
|
* All display strings resolve through `@oxyhq/core`'s
|
|
108
119
|
* `getAccountDisplayName` / `getAccountFallbackHandle` — no hand-rolled name
|
|
109
120
|
* fallbacks. Avatars pass the bare file id as `source` + `variant="thumb"`, so
|
|
@@ -208,8 +219,14 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
208
219
|
importantForAccessibility="no-hide-descendants"
|
|
209
220
|
>
|
|
210
221
|
<View
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
style={[
|
|
223
|
+
styles.skeletonCircle,
|
|
224
|
+
{
|
|
225
|
+
width: resolvedAvatarSize,
|
|
226
|
+
height: resolvedAvatarSize,
|
|
227
|
+
backgroundColor: colors.backgroundSecondary,
|
|
228
|
+
},
|
|
229
|
+
]}
|
|
213
230
|
/>
|
|
214
231
|
</View>
|
|
215
232
|
);
|
|
@@ -220,15 +237,21 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
220
237
|
const signInLabel = t('common.actions.signIn') || 'Sign in';
|
|
221
238
|
return (
|
|
222
239
|
<Pressable
|
|
223
|
-
className={
|
|
224
|
-
style={style}
|
|
240
|
+
className={className}
|
|
241
|
+
style={[styles.row, expanded && styles.rowExpanded, style]}
|
|
225
242
|
onPress={() => { void signIn(); }}
|
|
226
243
|
accessibilityRole="button"
|
|
227
244
|
accessibilityLabel={signInLabel}
|
|
228
245
|
>
|
|
229
246
|
<View
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
style={[
|
|
248
|
+
styles.avatarBadge,
|
|
249
|
+
{
|
|
250
|
+
width: resolvedAvatarSize,
|
|
251
|
+
height: resolvedAvatarSize,
|
|
252
|
+
backgroundColor: colors.backgroundSecondary,
|
|
253
|
+
},
|
|
254
|
+
]}
|
|
232
255
|
>
|
|
233
256
|
<MaterialCommunityIcons
|
|
234
257
|
name="login"
|
|
@@ -238,7 +261,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
238
261
|
</View>
|
|
239
262
|
{expanded ? (
|
|
240
263
|
<Text
|
|
241
|
-
|
|
264
|
+
style={[styles.signInLabel, { color: colors.text }]}
|
|
242
265
|
numberOfLines={1}
|
|
243
266
|
>
|
|
244
267
|
{signInLabel}
|
|
@@ -296,8 +319,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
296
319
|
<View className={className} style={style}>
|
|
297
320
|
<Pressable
|
|
298
321
|
ref={triggerRef}
|
|
299
|
-
|
|
300
|
-
style={collapsedBgStyle}
|
|
322
|
+
style={[styles.collapsedTrigger, collapsedBgStyle]}
|
|
301
323
|
onPress={openMenu}
|
|
302
324
|
accessibilityRole="button"
|
|
303
325
|
accessibilityLabel={accountLabel}
|
|
@@ -329,7 +351,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
329
351
|
// Slide the shrunk avatar left so its visual left edge stays put as it
|
|
330
352
|
// contracts. The avatar loses `size * (1 - scale)` of width; half of that
|
|
331
353
|
// (the left half, since scale is centered) is the offset, plus the row's
|
|
332
|
-
// left padding (
|
|
354
|
+
// left padding (8px) so it hugs the row edge like Bluesky's.
|
|
333
355
|
const activeAvatarTranslateX =
|
|
334
356
|
-(resolvedAvatarSize * (1 - ACTIVE_AVATAR_SCALE)) / 2 - 8;
|
|
335
357
|
|
|
@@ -378,23 +400,28 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
378
400
|
: undefined;
|
|
379
401
|
|
|
380
402
|
return (
|
|
381
|
-
<View className={
|
|
403
|
+
<View className={className} style={[styles.fullWidth, style]}>
|
|
382
404
|
<Pressable
|
|
383
405
|
ref={triggerRef}
|
|
384
|
-
|
|
385
|
-
style={rowStyle}
|
|
406
|
+
style={[styles.row, styles.rowExpanded, rowStyle]}
|
|
386
407
|
onPress={openMenu}
|
|
387
408
|
accessibilityRole="button"
|
|
388
409
|
accessibilityLabel={accountLabel}
|
|
389
410
|
{...webInteractionProps}
|
|
390
411
|
>
|
|
391
412
|
<View style={avatarWrapperStyle}>{avatarNode}</View>
|
|
392
|
-
<View
|
|
393
|
-
<Text
|
|
413
|
+
<View style={[styles.identity, identityStyle]}>
|
|
414
|
+
<Text
|
|
415
|
+
style={[styles.displayName, { color: colors.text }]}
|
|
416
|
+
numberOfLines={1}
|
|
417
|
+
>
|
|
394
418
|
{displayName}
|
|
395
419
|
</Text>
|
|
396
420
|
{handleLine ? (
|
|
397
|
-
<Text
|
|
421
|
+
<Text
|
|
422
|
+
style={[styles.handle, { color: colors.textSecondary }]}
|
|
423
|
+
numberOfLines={1}
|
|
424
|
+
>
|
|
398
425
|
{handleLine}
|
|
399
426
|
</Text>
|
|
400
427
|
) : null}
|
|
@@ -420,4 +447,55 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
|
|
|
420
447
|
);
|
|
421
448
|
};
|
|
422
449
|
|
|
450
|
+
const styles = StyleSheet.create({
|
|
451
|
+
fullWidth: {
|
|
452
|
+
width: '100%',
|
|
453
|
+
},
|
|
454
|
+
// Neutral skeleton / signed-out avatar circle (`rounded-full`).
|
|
455
|
+
skeletonCircle: {
|
|
456
|
+
borderRadius: 9999,
|
|
457
|
+
},
|
|
458
|
+
// Shared trigger row (`flex-row items-center gap-3 rounded-full px-2 py-2`).
|
|
459
|
+
row: {
|
|
460
|
+
flexDirection: 'row',
|
|
461
|
+
alignItems: 'center',
|
|
462
|
+
gap: 12,
|
|
463
|
+
borderRadius: 9999,
|
|
464
|
+
paddingHorizontal: 8,
|
|
465
|
+
paddingVertical: 8,
|
|
466
|
+
},
|
|
467
|
+
// `w-full` on the expanded row + its signed-out variant.
|
|
468
|
+
rowExpanded: {
|
|
469
|
+
width: '100%',
|
|
470
|
+
},
|
|
471
|
+
// Signed-out login badge (`items-center justify-center rounded-full`).
|
|
472
|
+
avatarBadge: {
|
|
473
|
+
alignItems: 'center',
|
|
474
|
+
justifyContent: 'center',
|
|
475
|
+
borderRadius: 9999,
|
|
476
|
+
},
|
|
477
|
+
// Signed-out label (`flex-1 font-semibold`).
|
|
478
|
+
signInLabel: {
|
|
479
|
+
flex: 1,
|
|
480
|
+
fontWeight: '600',
|
|
481
|
+
},
|
|
482
|
+
// Collapsed avatar-only trigger (`rounded-full`).
|
|
483
|
+
collapsedTrigger: {
|
|
484
|
+
borderRadius: 9999,
|
|
485
|
+
},
|
|
486
|
+
// Identity block (`min-w-0 flex-1`).
|
|
487
|
+
identity: {
|
|
488
|
+
flex: 1,
|
|
489
|
+
minWidth: 0,
|
|
490
|
+
},
|
|
491
|
+
// Display name (`font-bold`).
|
|
492
|
+
displayName: {
|
|
493
|
+
fontWeight: '700',
|
|
494
|
+
},
|
|
495
|
+
// Handle line (`text-xs`).
|
|
496
|
+
handle: {
|
|
497
|
+
fontSize: 12,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
|
|
423
501
|
export default ProfileButton;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Modal,
|
|
7
7
|
ScrollView,
|
|
8
8
|
ActivityIndicator,
|
|
9
|
+
StyleSheet,
|
|
9
10
|
Platform,
|
|
10
11
|
type ViewStyle,
|
|
11
12
|
} from 'react-native';
|
|
@@ -61,8 +62,9 @@ export interface ProfileMenuProps {
|
|
|
61
62
|
type ProfileMenuContentProps = Omit<ProfileMenuProps, 'open'>;
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
|
-
* Clean device-account switcher, modeled on the inbox `AccountMenu`
|
|
65
|
-
*
|
|
65
|
+
* Clean device-account switcher, modeled on the inbox `AccountMenu` and styled
|
|
66
|
+
* with react-native `StyleSheet` + the Bloom theme (via `useTheme`) so it
|
|
67
|
+
* renders in EVERY consumer regardless of NativeWind. Lists every account
|
|
66
68
|
* signed in on this device (from {@link useDeviceAccounts}); tapping a row
|
|
67
69
|
* switches, and each inactive row carries a sign-out icon. Below the list:
|
|
68
70
|
* Add account, Manage account, optional View profile, and Sign out of all.
|
|
@@ -175,8 +177,8 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
175
177
|
return (
|
|
176
178
|
<>
|
|
177
179
|
<ScrollView
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
style={styles.scroll}
|
|
181
|
+
contentContainerStyle={styles.scrollContent}
|
|
180
182
|
showsVerticalScrollIndicator={false}
|
|
181
183
|
>
|
|
182
184
|
{/* 1) Device accounts — active first (checkmark), others switchable. */}
|
|
@@ -192,9 +194,11 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
192
194
|
accessibilityState={{ selected: isActive }}
|
|
193
195
|
onPress={() => handleSwitch(account.sessionId)}
|
|
194
196
|
disabled={isActive || isBusy || isSwitching}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
style={[
|
|
198
|
+
styles.accountRow,
|
|
199
|
+
isActive && { backgroundColor: colors.backgroundSecondary },
|
|
200
|
+
isSwitching && !isActive && styles.rowDimmed,
|
|
201
|
+
]}
|
|
198
202
|
>
|
|
199
203
|
<Avatar
|
|
200
204
|
source={account.user.avatar ?? undefined}
|
|
@@ -203,16 +207,19 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
203
207
|
name={account.displayName}
|
|
204
208
|
size={isActive ? 40 : 32}
|
|
205
209
|
/>
|
|
206
|
-
<View
|
|
210
|
+
<View style={styles.accountInfo}>
|
|
207
211
|
<Text
|
|
208
|
-
|
|
212
|
+
style={[
|
|
213
|
+
isActive ? styles.accountNameActive : styles.accountName,
|
|
214
|
+
{ color: colors.text },
|
|
215
|
+
]}
|
|
209
216
|
numberOfLines={1}
|
|
210
217
|
>
|
|
211
218
|
{account.displayName}
|
|
212
219
|
</Text>
|
|
213
220
|
{account.email ? (
|
|
214
221
|
<Text
|
|
215
|
-
|
|
222
|
+
style={[styles.accountEmail, { color: colors.textSecondary }]}
|
|
216
223
|
numberOfLines={1}
|
|
217
224
|
>
|
|
218
225
|
{account.email}
|
|
@@ -235,7 +242,7 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
235
242
|
onPress={() => handleRemove(account.sessionId)}
|
|
236
243
|
disabled={actionDisabled}
|
|
237
244
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
238
|
-
|
|
245
|
+
style={styles.rowSignOutButton}
|
|
239
246
|
>
|
|
240
247
|
<MaterialCommunityIcons
|
|
241
248
|
name="logout"
|
|
@@ -250,9 +257,9 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
250
257
|
|
|
251
258
|
{/* 2) Switching indicator. */}
|
|
252
259
|
{isSwitching ? (
|
|
253
|
-
<View
|
|
260
|
+
<View style={styles.switchingRow}>
|
|
254
261
|
<ActivityIndicator color={colors.textSecondary} size="small" />
|
|
255
|
-
<Text
|
|
262
|
+
<Text style={[styles.switchingText, { color: colors.textSecondary }]}>
|
|
256
263
|
{t('accountMenu.switching') || 'Switching account…'}
|
|
257
264
|
</Text>
|
|
258
265
|
</View>
|
|
@@ -264,6 +271,7 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
264
271
|
<ActionRow
|
|
265
272
|
icon="account-plus-outline"
|
|
266
273
|
iconColor={colors.icon}
|
|
274
|
+
textColor={colors.text}
|
|
267
275
|
label={t('accountMenu.addAnother') || 'Add another account'}
|
|
268
276
|
disabled={actionDisabled}
|
|
269
277
|
onPress={() => {
|
|
@@ -276,6 +284,7 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
276
284
|
<ActionRow
|
|
277
285
|
icon="cog-outline"
|
|
278
286
|
iconColor={colors.icon}
|
|
287
|
+
textColor={colors.text}
|
|
279
288
|
label={t('accountMenu.manage') || 'Manage your Oxy Account'}
|
|
280
289
|
disabled={actionDisabled}
|
|
281
290
|
onPress={() => {
|
|
@@ -289,6 +298,7 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
289
298
|
<ActionRow
|
|
290
299
|
icon="account-outline"
|
|
291
300
|
iconColor={colors.icon}
|
|
301
|
+
textColor={colors.text}
|
|
292
302
|
label={t('accountMenu.viewProfile') || 'View profile'}
|
|
293
303
|
disabled={actionDisabled}
|
|
294
304
|
onPress={() => {
|
|
@@ -305,14 +315,14 @@ const ProfileMenuContent: React.FC<ProfileMenuContentProps> = ({
|
|
|
305
315
|
accessibilityLabel={t('accountMenu.signOutAll') || 'Sign out of all accounts'}
|
|
306
316
|
onPress={() => signOutAllDialog.open()}
|
|
307
317
|
disabled={actionDisabled}
|
|
308
|
-
|
|
318
|
+
style={[styles.signOutAllRow, actionDisabled && styles.rowDimmed]}
|
|
309
319
|
>
|
|
310
320
|
{signingOutAll ? (
|
|
311
321
|
<ActivityIndicator color={colors.error} size="small" />
|
|
312
322
|
) : (
|
|
313
323
|
<MaterialCommunityIcons name="logout" size={18} color={colors.error} />
|
|
314
324
|
)}
|
|
315
|
-
<Text
|
|
325
|
+
<Text style={[styles.actionText, { color: colors.error }]}>
|
|
316
326
|
{t('accountMenu.signOutAll') || 'Sign out of all accounts'}
|
|
317
327
|
</Text>
|
|
318
328
|
</Pressable>
|
|
@@ -357,6 +367,7 @@ const ProfileMenu: React.FC<ProfileMenuProps> = ({
|
|
|
357
367
|
onBeforeSessionChange,
|
|
358
368
|
}) => {
|
|
359
369
|
const { t } = useI18n();
|
|
370
|
+
const { colors } = useTheme();
|
|
360
371
|
|
|
361
372
|
// Web: anchor the panel to the measured trigger. When the anchor pins `top`
|
|
362
373
|
// the panel opens DOWNWARD from the trigger; otherwise it pins `bottom` and
|
|
@@ -385,19 +396,20 @@ const ProfileMenu: React.FC<ProfileMenuProps> = ({
|
|
|
385
396
|
accessibilityRole="button"
|
|
386
397
|
accessibilityLabel={t('common.actions.close') || 'Close'}
|
|
387
398
|
onPress={onClose}
|
|
388
|
-
|
|
389
|
-
style={!isWeb ? styles.nativeScrim : undefined}
|
|
399
|
+
style={isWeb ? styles.webOverlay : styles.nativeOverlay}
|
|
390
400
|
>
|
|
391
401
|
<Pressable
|
|
392
402
|
// Swallow taps inside the panel so they never reach the overlay's
|
|
393
403
|
// outside-tap-to-close handler.
|
|
394
404
|
onPress={() => undefined}
|
|
395
|
-
|
|
396
|
-
isWeb
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
405
|
+
style={[
|
|
406
|
+
isWeb ? styles.panelWeb : styles.panelNative,
|
|
407
|
+
{ backgroundColor: colors.background },
|
|
408
|
+
isWeb && { borderColor: colors.border },
|
|
409
|
+
panelAnchorStyle,
|
|
410
|
+
styles.panelBounds,
|
|
411
|
+
styles.shadow,
|
|
412
|
+
]}
|
|
401
413
|
accessibilityRole="menu"
|
|
402
414
|
accessibilityLabel={t('accountMenu.label') || 'Account menu'}
|
|
403
415
|
>
|
|
@@ -421,36 +433,136 @@ const ProfileMenu: React.FC<ProfileMenuProps> = ({
|
|
|
421
433
|
const ActionRow: React.FC<{
|
|
422
434
|
icon: React.ComponentProps<typeof MaterialCommunityIcons>['name'];
|
|
423
435
|
iconColor: string;
|
|
436
|
+
textColor: string;
|
|
424
437
|
label: string;
|
|
425
438
|
disabled: boolean;
|
|
426
439
|
onPress: () => void;
|
|
427
|
-
}> = ({ icon, iconColor, label, disabled, onPress }) => (
|
|
440
|
+
}> = ({ icon, iconColor, textColor, label, disabled, onPress }) => (
|
|
428
441
|
<Pressable
|
|
429
442
|
accessibilityRole="menuitem"
|
|
430
443
|
accessibilityLabel={label}
|
|
431
444
|
onPress={onPress}
|
|
432
445
|
disabled={disabled}
|
|
433
|
-
|
|
446
|
+
style={[styles.actionRow, disabled && styles.rowDimmed]}
|
|
434
447
|
>
|
|
435
448
|
<MaterialCommunityIcons name={icon} size={20} color={iconColor} />
|
|
436
|
-
<Text
|
|
449
|
+
<Text style={[styles.actionText, { color: textColor }]}>{label}</Text>
|
|
437
450
|
</Pressable>
|
|
438
451
|
);
|
|
439
452
|
|
|
440
|
-
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
453
|
+
const styles = StyleSheet.create({
|
|
454
|
+
// Overlay (`flex-1 relative` web / `flex-1 justify-end` + scrim native).
|
|
455
|
+
webOverlay: {
|
|
456
|
+
flex: 1,
|
|
457
|
+
position: 'relative',
|
|
458
|
+
},
|
|
459
|
+
nativeOverlay: {
|
|
460
|
+
flex: 1,
|
|
461
|
+
justifyContent: 'flex-end',
|
|
462
|
+
backgroundColor: 'rgba(0,0,0,0.32)',
|
|
463
|
+
},
|
|
464
|
+
// Panel shell — web popover (`overflow-hidden rounded-2xl border`).
|
|
465
|
+
panelWeb: {
|
|
466
|
+
overflow: 'hidden',
|
|
467
|
+
borderRadius: 16,
|
|
468
|
+
borderWidth: 1,
|
|
469
|
+
},
|
|
470
|
+
// Panel shell — native bottom sheet (`overflow-hidden rounded-t-3xl pb-3`).
|
|
471
|
+
panelNative: {
|
|
472
|
+
overflow: 'hidden',
|
|
473
|
+
borderTopLeftRadius: 24,
|
|
474
|
+
borderTopRightRadius: 24,
|
|
475
|
+
paddingBottom: 12,
|
|
476
|
+
},
|
|
477
|
+
// Shared `maxHeight: '85%'` cap applied to both panel variants.
|
|
478
|
+
panelBounds: {
|
|
479
|
+
maxHeight: '85%',
|
|
480
|
+
},
|
|
481
|
+
// The panel's drop shadow — dynamic elevation with no class equivalent.
|
|
444
482
|
shadow: {
|
|
445
483
|
shadowColor: '#000',
|
|
446
484
|
shadowOpacity: 0.18,
|
|
447
485
|
shadowRadius: 24,
|
|
448
486
|
shadowOffset: { width: 0, height: 8 },
|
|
449
487
|
elevation: 12,
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
488
|
+
},
|
|
489
|
+
// Scroll region (`grow-0` + `py-1` content).
|
|
490
|
+
scroll: {
|
|
491
|
+
flexGrow: 0,
|
|
492
|
+
},
|
|
493
|
+
scrollContent: {
|
|
494
|
+
paddingVertical: 4,
|
|
495
|
+
},
|
|
496
|
+
// Account row (`flex-row items-center gap-3 px-4 py-2.5`).
|
|
497
|
+
accountRow: {
|
|
498
|
+
flexDirection: 'row',
|
|
499
|
+
alignItems: 'center',
|
|
500
|
+
gap: 12,
|
|
501
|
+
paddingHorizontal: 16,
|
|
502
|
+
paddingVertical: 10,
|
|
503
|
+
},
|
|
504
|
+
// `opacity-40` dim applied to disabled / non-active-while-switching rows.
|
|
505
|
+
rowDimmed: {
|
|
506
|
+
opacity: 0.4,
|
|
507
|
+
},
|
|
508
|
+
// Identity block (`min-w-0 flex-1`).
|
|
509
|
+
accountInfo: {
|
|
510
|
+
flex: 1,
|
|
511
|
+
minWidth: 0,
|
|
512
|
+
},
|
|
513
|
+
// Inactive account name (`font-medium`).
|
|
514
|
+
accountName: {
|
|
515
|
+
fontWeight: '500',
|
|
516
|
+
},
|
|
517
|
+
// Active account name (`font-semibold`).
|
|
518
|
+
accountNameActive: {
|
|
519
|
+
fontWeight: '600',
|
|
520
|
+
},
|
|
521
|
+
// Secondary email line (`text-xs`).
|
|
522
|
+
accountEmail: {
|
|
523
|
+
fontSize: 12,
|
|
524
|
+
},
|
|
525
|
+
// Per-row sign-out button (`h-7 w-7 items-center justify-center rounded-full opacity-60`).
|
|
526
|
+
rowSignOutButton: {
|
|
527
|
+
width: 28,
|
|
528
|
+
height: 28,
|
|
529
|
+
alignItems: 'center',
|
|
530
|
+
justifyContent: 'center',
|
|
531
|
+
borderRadius: 9999,
|
|
532
|
+
opacity: 0.6,
|
|
533
|
+
},
|
|
534
|
+
// Switching indicator (`flex-row items-center justify-center gap-2 py-2`).
|
|
535
|
+
switchingRow: {
|
|
536
|
+
flexDirection: 'row',
|
|
537
|
+
alignItems: 'center',
|
|
538
|
+
justifyContent: 'center',
|
|
539
|
+
gap: 8,
|
|
540
|
+
paddingVertical: 8,
|
|
541
|
+
},
|
|
542
|
+
switchingText: {
|
|
543
|
+
fontSize: 12,
|
|
544
|
+
fontWeight: '500',
|
|
545
|
+
},
|
|
546
|
+
// Bottom action rows (`flex-row items-center gap-3 px-4 py-3`).
|
|
547
|
+
actionRow: {
|
|
548
|
+
flexDirection: 'row',
|
|
549
|
+
alignItems: 'center',
|
|
550
|
+
gap: 12,
|
|
551
|
+
paddingHorizontal: 16,
|
|
552
|
+
paddingVertical: 12,
|
|
553
|
+
},
|
|
554
|
+
// Sign-out-of-all row shares the action-row geometry.
|
|
555
|
+
signOutAllRow: {
|
|
556
|
+
flexDirection: 'row',
|
|
557
|
+
alignItems: 'center',
|
|
558
|
+
gap: 12,
|
|
559
|
+
paddingHorizontal: 16,
|
|
560
|
+
paddingVertical: 12,
|
|
561
|
+
},
|
|
562
|
+
// Action / sign-out label (`font-medium`).
|
|
563
|
+
actionText: {
|
|
564
|
+
fontWeight: '500',
|
|
565
|
+
},
|
|
566
|
+
});
|
|
455
567
|
|
|
456
568
|
export default ProfileMenu;
|