@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.
@@ -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,EAIH,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,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqT/C,CAAC;AAEF,eAAe,aAAa,CAAC"}
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;AA2B/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;AAwRD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAoE3C,CAAC;AAsCF,eAAe,WAAW,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
- /** Extra className applied to the outer trigger. */
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,EAIH,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,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqT/C,CAAC;AAEF,eAAe,aAAa,CAAC"}
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;AA2B/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;AAwRD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAoE3C,CAAC;AAsCF,eAAe,WAAW,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "13.2.0",
3
+ "version": "13.3.0",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -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
- /** Extra className applied to the outer trigger. */
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
- className="rounded-full bg-secondary"
212
- style={{ width: resolvedAvatarSize, height: resolvedAvatarSize }}
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={`${expanded ? 'w-full ' : ''}flex-row items-center gap-3 rounded-full px-2 py-2 ${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
- className="items-center justify-center rounded-full bg-secondary"
231
- style={{ width: resolvedAvatarSize, height: resolvedAvatarSize }}
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
- className="flex-1 font-semibold text-foreground"
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
- className="rounded-full"
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 (px-2 = 8px) so it hugs the row edge like Bluesky's.
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={`w-full ${className ?? ''}`} style={style}>
403
+ <View className={className} style={[styles.fullWidth, style]}>
382
404
  <Pressable
383
405
  ref={triggerRef}
384
- className="w-full flex-row items-center gap-3 rounded-full px-2 py-2"
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 className="min-w-0 flex-1" style={identityStyle}>
393
- <Text className="font-bold text-foreground" numberOfLines={1}>
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 className="text-xs text-muted-foreground" numberOfLines={1}>
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` but written
65
- * fresh with NativeWind classNames + Bloom primitives. Lists every account
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
- className="grow-0"
179
- contentContainerClassName="py-1"
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
- className={`flex-row items-center gap-3 px-4 py-2.5 ${
196
- isActive ? 'bg-secondary' : ''
197
- } ${isSwitching && !isActive ? 'opacity-40' : ''}`}
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 className="min-w-0 flex-1">
210
+ <View style={styles.accountInfo}>
207
211
  <Text
208
- className={`text-foreground ${isActive ? 'font-semibold' : 'font-medium'}`}
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
- className="text-xs text-muted-foreground"
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
- className="h-7 w-7 items-center justify-center rounded-full opacity-60"
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 className="flex-row items-center justify-center gap-2 py-2">
260
+ <View style={styles.switchingRow}>
254
261
  <ActivityIndicator color={colors.textSecondary} size="small" />
255
- <Text className="text-xs font-medium text-muted-foreground">
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
- className={`flex-row items-center gap-3 px-4 py-3 ${actionDisabled ? 'opacity-40' : ''}`}
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 className="font-medium" style={{ color: colors.error }}>
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
- className={isWeb ? 'flex-1 relative' : 'flex-1 justify-end'}
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
- className={
396
- isWeb
397
- ? 'overflow-hidden rounded-2xl border border-border bg-background'
398
- : 'overflow-hidden rounded-t-3xl bg-background pb-3'
399
- }
400
- style={[panelAnchorStyle, { maxHeight: '85%' }, styles.shadow]}
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
- className={`flex-row items-center gap-3 px-4 py-3 ${disabled ? 'opacity-40' : ''}`}
446
+ style={[styles.actionRow, disabled && styles.rowDimmed]}
434
447
  >
435
448
  <MaterialCommunityIcons name={icon} size={20} color={iconColor} />
436
- <Text className="font-medium text-foreground">{label}</Text>
449
+ <Text style={[styles.actionText, { color: textColor }]}>{label}</Text>
437
450
  </Pressable>
438
451
  );
439
452
 
440
- // The panel's drop shadow and the native scrim are the only values with no
441
- // NativeWind class equivalent in this package (dynamic elevation + rgba scrim),
442
- // so they stay as small inline objects rather than raw class-replaceable styles.
443
- const styles = {
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
- } satisfies ViewStyle,
451
- nativeScrim: {
452
- backgroundColor: 'rgba(0,0,0,0.32)',
453
- } satisfies ViewStyle,
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;