@oxy.so/services 8.0.0 → 8.1.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.
Files changed (60) hide show
  1. package/lib/commonjs/assets/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
  2. package/lib/commonjs/assets/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
  3. package/lib/commonjs/assets/assets/fonts/icons/manifest.json +4 -5
  4. package/lib/commonjs/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
  5. package/lib/commonjs/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
  6. package/lib/commonjs/assets/fonts/icons/manifest.json +4 -5
  7. package/lib/commonjs/ui/components/authChooser/primitives.js +1 -102
  8. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  9. package/lib/commonjs/ui/components/authChooser/styles.js +0 -33
  10. package/lib/commonjs/ui/components/authChooser/styles.js.map +1 -1
  11. package/lib/commonjs/ui/components/signIn/OxyAccountPicker.js +151 -56
  12. package/lib/commonjs/ui/components/signIn/OxyAccountPicker.js.map +1 -1
  13. package/lib/commonjs/ui/icons/subsetGlyphMaps.js +0 -1
  14. package/lib/commonjs/ui/icons/subsetGlyphMaps.js.map +1 -1
  15. package/lib/module/assets/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
  16. package/lib/module/assets/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
  17. package/lib/module/assets/assets/fonts/icons/manifest.json +4 -5
  18. package/lib/module/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
  19. package/lib/module/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
  20. package/lib/module/assets/fonts/icons/manifest.json +4 -5
  21. package/lib/module/ui/components/authChooser/primitives.js +2 -103
  22. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  23. package/lib/module/ui/components/authChooser/styles.js +0 -33
  24. package/lib/module/ui/components/authChooser/styles.js.map +1 -1
  25. package/lib/module/ui/components/signIn/OxyAccountPicker.js +154 -57
  26. package/lib/module/ui/components/signIn/OxyAccountPicker.js.map +1 -1
  27. package/lib/module/ui/icons/subsetGlyphMaps.js +0 -1
  28. package/lib/module/ui/icons/subsetGlyphMaps.js.map +1 -1
  29. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +1 -34
  30. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/ui/components/authChooser/styles.d.ts +0 -33
  32. package/lib/typescript/commonjs/ui/components/authChooser/styles.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/ui/components/signIn/OxyAccountPicker.d.ts +9 -3
  34. package/lib/typescript/commonjs/ui/components/signIn/OxyAccountPicker.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
  36. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts +1 -1
  38. package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/icons/subsetGlyphMaps.d.ts +0 -1
  40. package/lib/typescript/commonjs/ui/icons/subsetGlyphMaps.d.ts.map +1 -1
  41. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +1 -34
  42. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  43. package/lib/typescript/module/ui/components/authChooser/styles.d.ts +0 -33
  44. package/lib/typescript/module/ui/components/authChooser/styles.d.ts.map +1 -1
  45. package/lib/typescript/module/ui/components/signIn/OxyAccountPicker.d.ts +9 -3
  46. package/lib/typescript/module/ui/components/signIn/OxyAccountPicker.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/icons/MaterialCommunityIcons.d.ts +1 -1
  50. package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/icons/subsetGlyphMaps.d.ts +0 -1
  52. package/lib/typescript/module/ui/icons/subsetGlyphMaps.d.ts.map +1 -1
  53. package/package.json +2 -2
  54. package/src/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
  55. package/src/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
  56. package/src/assets/fonts/icons/manifest.json +4 -5
  57. package/src/ui/components/authChooser/primitives.tsx +2 -96
  58. package/src/ui/components/authChooser/styles.ts +0 -33
  59. package/src/ui/components/signIn/OxyAccountPicker.tsx +130 -65
  60. package/src/ui/icons/subsetGlyphMaps.ts +0 -1
@@ -13,39 +13,6 @@ const QR_PLATE_BG = '#FFFFFF';
13
13
  * text-link rhythm every view's fallback affordances share.
14
14
  */
15
15
  export const authChooserStyles = StyleSheet.create({
16
- rows: {
17
- width: '100%',
18
- gap: 8,
19
- },
20
- accountRow: {
21
- flexDirection: 'row',
22
- alignItems: 'center',
23
- gap: 12,
24
- borderWidth: StyleSheet.hairlineWidth,
25
- borderRadius: 16,
26
- paddingVertical: 10,
27
- paddingHorizontal: 12,
28
- },
29
- rowDisabled: {
30
- opacity: 0.6,
31
- },
32
- avatarRing: {
33
- borderRadius: 9999,
34
- borderWidth: 2,
35
- padding: 1,
36
- },
37
- rowMeta: {
38
- flex: 1,
39
- minWidth: 0,
40
- },
41
- rowName: {
42
- fontSize: 15,
43
- fontWeight: '600',
44
- },
45
- rowHandle: {
46
- fontSize: 12.5,
47
- marginTop: 1,
48
- },
49
16
  /**
50
17
  * Animated-collapse container for the account switch list — the outer clip and
51
18
  * the inner measuring wrapper. Style-based (NOT NativeWind): the container
@@ -8,20 +8,34 @@
8
8
  * means — a switch, a "Continue as" sign-in, or activating the pair before an
9
9
  * OAuth consent — and passes the pair back, never an account id, which cannot
10
10
  * say whose route was chosen.
11
+ *
12
+ * One rounded list: each row is the account's name and `@handle` on the left,
13
+ * its avatar and a chevron on the right, with a hairline between rows that
14
+ * steps aside for a hovered row. Layout is NativeWind; hover is driven by
15
+ * `onHoverIn`/`onHoverOut` (web only), because this pipeline does not emit
16
+ * `hover:` variants — the same pattern as the account menu.
11
17
  */
12
18
 
13
19
  import type React from 'react';
14
- import { Pressable, StyleSheet, View } from 'react-native';
20
+ import { Fragment, useState } from 'react';
21
+ import { ScrollView } from 'react-native';
22
+ import { Pressable, View } from 'react-native-css/components';
23
+ import { Avatar } from '@oxy.so/bloom/avatar';
15
24
  import { RiArrowRightSLine } from '@oxy.so/bloom/icons/RiArrowRightSLine';
25
+ import { RiCheckLine } from '@oxy.so/bloom/icons/RiCheckLine';
26
+ import { RiLoader4Line } from '@oxy.so/bloom/icons/RiLoader4Line';
16
27
  import { RiUserAddLine } from '@oxy.so/bloom/icons/RiUserAddLine';
17
28
  import { useTheme } from '@oxy.so/bloom/theme';
18
29
  import { Text } from '@oxy.so/bloom/typography';
19
30
  import { showsPrincipalHeaders, type SwitcherContextRow, type SwitcherPrincipalRow } from '@oxy.so/core/session';
20
31
  import { useI18n } from '../../hooks/useI18n';
21
- import { AccountRow } from '../authChooser/primitives';
22
- import { authChooserStyles } from '../authChooser/styles';
32
+ import { resolveAccentHex } from '../authChooser/types';
23
33
  import { OxyAuthScreen, OxyAuthScreenHeader, OxyAuthTerms } from './OxyAuthScreen';
24
34
 
35
+ /** The list scrolls past this height (`max-h-96`). */
36
+ const LIST_MAX_HEIGHT = 384;
37
+ const AVATAR_SIZE = 40;
38
+
25
39
  export interface OxyAccountPickerProps {
26
40
  /** The device's people, each with the accounts they may act as (`useDeviceSwitcher`). */
27
41
  principals: SwitcherPrincipalRow[];
@@ -35,9 +49,9 @@ export interface OxyAccountPickerProps {
35
49
  /** Disables every row while a choice is in flight. */
36
50
  isLoading?: boolean;
37
51
  /**
38
- * This origin holds no session. A row then reads "Continue as @handle" and
39
- * never as the current account: the device can list an identity as active
40
- * while nobody is signed in HERE.
52
+ * This origin holds no session. A row is then announced as "Continue as
53
+ * @handle" and never marked as the current account: the device can list an
54
+ * identity as active while nobody is signed in HERE.
41
55
  */
42
56
  signedOut?: boolean;
43
57
  }
@@ -53,77 +67,128 @@ export const OxyAccountPicker: React.FC<OxyAccountPickerProps> = ({
53
67
  }) => {
54
68
  const theme = useTheme();
55
69
  const { t } = useI18n();
70
+ const [hovered, setHovered] = useState<number | null>(null);
56
71
  // Whose route a row is only needs naming once someone holds more than one
57
72
  // account here — the same rule the account menu's group headers follow.
58
73
  const namesTheOperator = showsPrincipalHeaders(principals);
59
74
 
75
+ const rows = principals.flatMap((principal) =>
76
+ principal.contexts.map((context) => ({
77
+ context,
78
+ operatedBy:
79
+ namesTheOperator && context.isDelegated
80
+ ? t('accountSwitcher.context.operatedBy', { name: principal.displayName })
81
+ : null,
82
+ })),
83
+ );
84
+ const useAnotherIndex = rows.length;
85
+
86
+ /** The hairline under row `index`, hidden while either neighbour is hovered. */
87
+ const divider = (index: number) =>
88
+ hovered === index || hovered === index + 1 ? (
89
+ <View className="h-px mx-[8px]" />
90
+ ) : (
91
+ <View className="h-px mx-[8px] bg-border opacity-50" />
92
+ );
93
+
94
+ const rowClassName = (index: number, disabled: boolean) =>
95
+ `flex-row items-center gap-[12px] rounded-[14px] p-[8px] ${hovered === index && !disabled ? 'bg-fill' : ''} ${disabled ? 'opacity-50' : ''}`;
96
+
60
97
  return (
61
98
  <OxyAuthScreen>
62
99
  <OxyAuthScreenHeader
63
100
  title={t('signin.chooser.title')}
64
101
  description={appName ? t('signin.chooser.subtitleToApp', { app: appName }) : t('signin.chooser.subtitle')}
65
102
  />
66
- <View style={authChooserStyles.rows}>
67
- {principals.flatMap((principal) =>
68
- principal.contexts.map((context) => (
69
- <AccountRow
70
- key={context.contextId}
71
- context={context}
72
- operatedBy={
73
- namesTheOperator && context.isDelegated
74
- ? t('accountSwitcher.context.operatedBy', { name: principal.displayName })
75
- : null
76
- }
77
- continueAsLabel={
78
- signedOut
79
- ? t('signin.chooser.continueAs', {
80
- name: context.handle ? `@${context.handle}` : context.displayName,
81
- })
82
- : null
83
- }
84
- theme={theme}
85
- activating={pendingContextId === context.contextId}
86
- disabled={isLoading}
87
- onPress={() => onSelectContext(context)}
88
- />
89
- )),
90
- )}
91
- <Pressable
92
- onPress={onUseAnother}
93
- disabled={isLoading}
94
- accessibilityRole="button"
95
- accessibilityLabel={t('signin.chooser.useAnother')}
96
- style={[
97
- authChooserStyles.accountRow,
98
- { borderColor: theme.colors.border, backgroundColor: theme.colors.card },
99
- isLoading ? authChooserStyles.rowDisabled : null,
100
- ]}
101
- testID="use-another-account"
102
- >
103
- <View style={[styles.addGlyph, { backgroundColor: theme.colors.backgroundSecondary }]}>
104
- <RiUserAddLine size="md" fill={theme.colors.textSecondary} />
105
- </View>
106
- <Text style={[authChooserStyles.rowName, styles.addLabel, { color: theme.colors.text }]}>
107
- {t('signin.chooser.useAnother')}
108
- </Text>
109
- <RiArrowRightSLine size="md" fill={theme.colors.textSecondary} />
110
- </Pressable>
103
+ <View className="bg-fill-secondary rounded-[22px] overflow-hidden" testID="account-picker-list">
104
+ <ScrollView style={{ maxHeight: LIST_MAX_HEIGHT }} contentContainerStyle={{ padding: 8 }}>
105
+ {rows.map(({ context, operatedBy }, index) => {
106
+ const activating = pendingContextId === context.contextId;
107
+ const disabled = isLoading || !context.canActivate;
108
+ const current = !signedOut && context.isActive;
109
+ const accent = resolveAccentHex(context.color, theme.colors.primary);
110
+ const handle = context.handle ? `@${context.handle}` : null;
111
+ const label = signedOut
112
+ ? t('signin.chooser.continueAs', { name: handle ?? context.displayName })
113
+ : context.displayName;
114
+ return (
115
+ <Fragment key={context.contextId}>
116
+ <Pressable
117
+ className={rowClassName(index, disabled && !activating)}
118
+ onPress={() => onSelectContext(context)}
119
+ onHoverIn={() => setHovered(index)}
120
+ onHoverOut={() => setHovered((value) => (value === index ? null : value))}
121
+ disabled={disabled}
122
+ accessibilityRole="button"
123
+ accessibilityLabel={label}
124
+ accessibilityState={{ selected: current, disabled, busy: activating }}
125
+ >
126
+ <View className="flex-1 min-w-0">
127
+ <Text className="text-body text-text" style={{ fontWeight: '500' }} numberOfLines={1}>
128
+ {context.displayName}
129
+ </Text>
130
+ {operatedBy || handle ? (
131
+ <Text className="text-bodySmall text-text-secondary" numberOfLines={1}>
132
+ {operatedBy ?? handle}
133
+ </Text>
134
+ ) : null}
135
+ </View>
136
+ <View className="flex-row items-center gap-[8px] shrink-0">
137
+ <View style={{ width: AVATAR_SIZE, height: AVATAR_SIZE }}>
138
+ <Avatar
139
+ source={context.avatarUrl ?? undefined}
140
+ variant="thumb"
141
+ name={context.displayName}
142
+ size={AVATAR_SIZE}
143
+ />
144
+ {current ? (
145
+ <View
146
+ className="absolute -bottom-[2px] -right-[2px] items-center justify-center rounded-full border-2 border-background"
147
+ style={{ width: 18, height: 18, backgroundColor: accent }}
148
+ >
149
+ <RiCheckLine size="xs" fill="#ffffff" />
150
+ </View>
151
+ ) : null}
152
+ </View>
153
+ {activating ? (
154
+ <RiLoader4Line size="sm" fill={accent} />
155
+ ) : (
156
+ <RiArrowRightSLine size="sm" fill={theme.colors.textSecondary} />
157
+ )}
158
+ </View>
159
+ </Pressable>
160
+ {divider(index)}
161
+ </Fragment>
162
+ );
163
+ })}
164
+ <Pressable
165
+ className={rowClassName(useAnotherIndex, isLoading)}
166
+ onPress={onUseAnother}
167
+ onHoverIn={() => setHovered(useAnotherIndex)}
168
+ onHoverOut={() => setHovered((value) => (value === useAnotherIndex ? null : value))}
169
+ disabled={isLoading}
170
+ accessibilityRole="button"
171
+ accessibilityLabel={t('signin.chooser.useAnother')}
172
+ testID="use-another-account"
173
+ >
174
+ <View className="flex-1 min-w-0">
175
+ <Text className="text-body text-text" style={{ fontWeight: '500' }} numberOfLines={1}>
176
+ {t('signin.chooser.useAnother')}
177
+ </Text>
178
+ </View>
179
+ <View className="flex-row items-center gap-[8px] shrink-0">
180
+ <View
181
+ className="bg-fill items-center justify-center rounded-full"
182
+ style={{ width: AVATAR_SIZE, height: AVATAR_SIZE }}
183
+ >
184
+ <RiUserAddLine size="md" fill={theme.colors.textSecondary} />
185
+ </View>
186
+ <RiArrowRightSLine size="sm" fill={theme.colors.textSecondary} />
187
+ </View>
188
+ </Pressable>
189
+ </ScrollView>
111
190
  </View>
112
191
  <OxyAuthTerms />
113
192
  </OxyAuthScreen>
114
193
  );
115
194
  };
116
-
117
- const styles = StyleSheet.create({
118
- // Matches `AccountRow`'s 40px avatar plus its 2px ring and 1px gap.
119
- addGlyph: {
120
- width: 46,
121
- height: 46,
122
- borderRadius: 23,
123
- alignItems: 'center',
124
- justifyContent: 'center',
125
- },
126
- addLabel: {
127
- flex: 1,
128
- },
129
- });
@@ -186,7 +186,6 @@ export const materialCommunityIconsGlyphMap = {
186
186
  "check-all": 983341,
187
187
  "check-circle": 984544,
188
188
  "chevron-down": 983360,
189
- "chevron-right": 983362,
190
189
  "chevron-up": 983363,
191
190
  "chip": 984602,
192
191
  "circle-outline": 984934,