@oxyhq/services 22.8.7 → 22.9.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 (116) hide show
  1. package/lib/commonjs/ui/components/OxyAccountDialog.js +30 -39
  2. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -1
  3. package/lib/commonjs/ui/components/OxyProvider.js +9 -29
  4. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  5. package/lib/commonjs/ui/components/SurfaceScreen.js +146 -0
  6. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -0
  7. package/lib/commonjs/ui/context/OxyContext.js +32 -1
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/navigation/bottomSheetManager.js +38 -56
  10. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  11. package/lib/commonjs/ui/navigation/routes.js +7 -57
  12. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  13. package/lib/commonjs/ui/navigation/surfaceNavStack.js +98 -0
  14. package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -0
  15. package/lib/commonjs/ui/navigation/surfaceRegistry.js +108 -0
  16. package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -0
  17. package/lib/commonjs/ui/navigation/surfaces.js +219 -0
  18. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -0
  19. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +9 -7
  20. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  21. package/lib/commonjs/ui/types/surfaceScreen.js +6 -0
  22. package/lib/commonjs/ui/types/surfaceScreen.js.map +1 -0
  23. package/lib/module/ui/components/OxyAccountDialog.js +31 -40
  24. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -1
  25. package/lib/module/ui/components/OxyProvider.js +10 -30
  26. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  27. package/lib/module/ui/components/SurfaceScreen.js +141 -0
  28. package/lib/module/ui/components/SurfaceScreen.js.map +1 -0
  29. package/lib/module/ui/context/OxyContext.js +32 -1
  30. package/lib/module/ui/context/OxyContext.js.map +1 -1
  31. package/lib/module/ui/navigation/bottomSheetManager.js +37 -53
  32. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  33. package/lib/module/ui/navigation/routes.js +6 -56
  34. package/lib/module/ui/navigation/routes.js.map +1 -1
  35. package/lib/module/ui/navigation/surfaceNavStack.js +95 -0
  36. package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -0
  37. package/lib/module/ui/navigation/surfaceRegistry.js +102 -0
  38. package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -0
  39. package/lib/module/ui/navigation/surfaces.js +207 -0
  40. package/lib/module/ui/navigation/surfaces.js.map +1 -0
  41. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +8 -7
  42. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  43. package/lib/module/ui/types/surfaceScreen.js +4 -0
  44. package/lib/module/ui/types/surfaceScreen.js.map +1 -0
  45. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +29 -27
  46. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +3 -2
  48. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +24 -0
  50. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +24 -16
  53. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +2 -60
  55. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +51 -0
  57. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +117 -0
  59. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +79 -0
  61. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/ui/types/surfaceScreen.d.ts +38 -0
  64. package/lib/typescript/commonjs/ui/types/surfaceScreen.d.ts.map +1 -0
  65. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +29 -27
  66. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/components/OxyProvider.d.ts +3 -2
  68. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  69. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +24 -0
  70. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -0
  71. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  72. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +24 -16
  73. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  74. package/lib/typescript/module/ui/navigation/routes.d.ts +2 -60
  75. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  76. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +51 -0
  77. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -0
  78. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +117 -0
  79. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -0
  80. package/lib/typescript/module/ui/navigation/surfaces.d.ts +79 -0
  81. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -0
  82. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  83. package/lib/typescript/module/ui/types/surfaceScreen.d.ts +38 -0
  84. package/lib/typescript/module/ui/types/surfaceScreen.d.ts.map +1 -0
  85. package/package.json +2 -2
  86. package/src/ui/components/OxyAccountDialog.tsx +32 -38
  87. package/src/ui/components/OxyProvider.tsx +12 -34
  88. package/src/ui/components/SurfaceScreen.tsx +166 -0
  89. package/src/ui/context/OxyContext.tsx +27 -1
  90. package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +39 -0
  91. package/src/ui/navigation/bottomSheetManager.ts +46 -73
  92. package/src/ui/navigation/routes.ts +7 -106
  93. package/src/ui/navigation/surfaceNavStack.ts +110 -0
  94. package/src/ui/navigation/surfaceRegistry.ts +174 -0
  95. package/src/ui/navigation/surfaces.ts +244 -0
  96. package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +9 -7
  97. package/src/ui/types/surfaceScreen.ts +38 -0
  98. package/lib/commonjs/ui/components/BottomSheet.js +0 -49
  99. package/lib/commonjs/ui/components/BottomSheet.js.map +0 -1
  100. package/lib/commonjs/ui/components/BottomSheetRouter.js +0 -298
  101. package/lib/commonjs/ui/components/BottomSheetRouter.js.map +0 -1
  102. package/lib/module/ui/components/BottomSheet.js +0 -45
  103. package/lib/module/ui/components/BottomSheet.js.map +0 -1
  104. package/lib/module/ui/components/BottomSheetRouter.js +0 -292
  105. package/lib/module/ui/components/BottomSheetRouter.js.map +0 -1
  106. package/lib/typescript/commonjs/ui/components/BottomSheet.d.ts +0 -29
  107. package/lib/typescript/commonjs/ui/components/BottomSheet.d.ts.map +0 -1
  108. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +0 -9
  109. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +0 -1
  110. package/lib/typescript/module/ui/components/BottomSheet.d.ts +0 -29
  111. package/lib/typescript/module/ui/components/BottomSheet.d.ts.map +0 -1
  112. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +0 -9
  113. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +0 -1
  114. package/src/ui/components/BottomSheet.tsx +0 -48
  115. package/src/ui/components/BottomSheetRouter.tsx +0 -310
  116. package/src/ui/navigation/__tests__/getSheetConfig.test.ts +0 -36
@@ -1,310 +0,0 @@
1
- import React, { useRef, useEffect, useCallback, useMemo, type ErrorInfo } from 'react';
2
- import { BackHandler, Platform, View, StyleSheet, Text, type StyleProp, type ViewStyle } from 'react-native';
3
- import { useStore } from 'zustand';
4
- import type { RouteName } from '../navigation/routes';
5
- import { getScreenComponent, getSheetConfig, isValidRoute } from '../navigation/routes';
6
- import type { BaseScreenProps } from '../types/navigation';
7
- import { useTheme } from '@oxyhq/bloom/theme';
8
- import { Dialog } from '@oxyhq/bloom/dialog';
9
- import BottomSheet, { type BottomSheetRef } from './BottomSheet';
10
- import {
11
- bottomSheetStore,
12
- getState,
13
- showBottomSheet,
14
- closeBottomSheet,
15
- goBack,
16
- updateState,
17
- } from '../navigation/bottomSheetManager';
18
-
19
- /** Error boundary to catch screen rendering failures (e.g. lazy require() throws) */
20
- interface ScreenErrorBoundaryState {
21
- error: Error | null;
22
- }
23
-
24
- class ScreenErrorBoundary extends React.Component<
25
- { screenName: string; children: React.ReactNode },
26
- ScreenErrorBoundaryState
27
- > {
28
- state: ScreenErrorBoundaryState = { error: null };
29
-
30
- static getDerivedStateFromError(error: Error): ScreenErrorBoundaryState {
31
- return { error };
32
- }
33
-
34
- componentDidCatch(error: Error, info: ErrorInfo): void {
35
- if (__DEV__) {
36
- console.error(
37
- `[BottomSheetRouter] Screen "${this.props.screenName}" crashed:`,
38
- error,
39
- info.componentStack,
40
- );
41
- }
42
- }
43
-
44
- componentDidUpdate(prevProps: { screenName: string }): void {
45
- if (prevProps.screenName !== this.props.screenName && this.state.error) {
46
- this.setState({ error: null });
47
- }
48
- }
49
-
50
- render(): React.ReactNode {
51
- if (this.state.error) {
52
- return (
53
- <View style={errorStyles.container}>
54
- <Text style={errorStyles.title}>Something went wrong</Text>
55
- {__DEV__ && (
56
- <Text style={errorStyles.message}>{this.state.error.message}</Text>
57
- )}
58
- </View>
59
- );
60
- }
61
- return this.props.children;
62
- }
63
- }
64
-
65
- const errorStyles = StyleSheet.create({
66
- container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },
67
- title: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
68
- message: { fontSize: 13, textAlign: 'center' },
69
- });
70
-
71
- /**
72
- * Responsive placement for `presentation: 'dialog'` routes: a bottom-sheet on
73
- * narrow viewports, a centered card at `md+` (≥768dp). Module-scoped so the
74
- * object identity is stable across renders (Bloom's placement resolver memoizes
75
- * on it). PhotoPickerSection keys its center-vs-bottom layout off the SAME `md`
76
- * breakpoint — keep the two in sync if this map changes.
77
- */
78
- const DIALOG_PLACEMENT = { base: 'bottom', md: 'center' } as const;
79
-
80
- export interface BottomSheetRouterProps {
81
- onScreenChange?: (screen: RouteName | null) => void;
82
- onDismiss?: () => void;
83
- }
84
-
85
- /**
86
- * BottomSheetRouter - Navigation container for bottom sheet screens
87
- */
88
- const BottomSheetRouter: React.FC<BottomSheetRouterProps> = ({ onScreenChange, onDismiss }) => {
89
- const sheetRef = useRef<BottomSheetRef>(null);
90
- const theme = useTheme();
91
- const prevScreenRef = useRef<RouteName | null>(null);
92
-
93
- const { currentScreen, screenProps, currentStep, isOpen } = useStore(bottomSheetStore);
94
-
95
- const ScreenComponent = useMemo(() => {
96
- if (!currentScreen) return null;
97
- const component = getScreenComponent(currentScreen);
98
- if (__DEV__ && !component) {
99
- console.error(
100
- `[BottomSheetRouter] getScreenComponent("${currentScreen}") returned undefined — the screen's lazy require() likely failed.`,
101
- );
102
- }
103
- return component ?? null;
104
- }, [currentScreen]);
105
-
106
- // Route-level sheet config (which surface hosts the route, whether the sheet
107
- // provides its own ScrollView, etc.). Computed early so the visibility
108
- // effect can gate the in-tree sheet on the resolved presentation.
109
- const sheetConfig = useMemo(
110
- () => getSheetConfig(currentScreen, screenProps),
111
- [currentScreen, screenProps],
112
- );
113
- const isDialogPresentation = sheetConfig.presentation === 'dialog';
114
-
115
- // Notify screen changes
116
- useEffect(() => {
117
- if (prevScreenRef.current !== currentScreen) {
118
- onScreenChange?.(currentScreen);
119
- prevScreenRef.current = currentScreen;
120
- }
121
- }, [currentScreen, onScreenChange]);
122
-
123
- // Control visibility. The in-tree BottomSheet only ever presents for
124
- // 'sheet'-presentation routes — 'dialog' routes render in the Bloom
125
- // `<Dialog>` below, so the sheet stays dismissed for them (both surfaces
126
- // remain mounted, so the pick→crop dialog→sheet handoff has no unmount jank).
127
- useEffect(() => {
128
- if (!sheetRef.current) return;
129
-
130
- if (isOpen && !isDialogPresentation) {
131
- sheetRef.current.present();
132
- } else {
133
- sheetRef.current.dismiss();
134
- }
135
- }, [isOpen, isDialogPresentation]);
136
-
137
- // Android back button
138
- useEffect(() => {
139
- if (!isOpen) return;
140
- if (Platform.OS !== 'android') return;
141
- const handler = BackHandler.addEventListener('hardwareBackPress', () => {
142
- handleGoBack();
143
- return true;
144
- });
145
- return () => handler.remove();
146
- }, [isOpen]);
147
-
148
- const navigate = useCallback((screen: RouteName, props?: Record<string, unknown>) => {
149
- if (!isValidRoute(screen)) {
150
- if (__DEV__) console.warn(`[BottomSheetRouter] Invalid route: ${screen}`);
151
- return;
152
- }
153
- showBottomSheet({ screen, props });
154
- }, []);
155
-
156
- const handleGoBack = useCallback(() => {
157
- const state = getState();
158
-
159
- // Try history first
160
- if (state.history.length > 0) {
161
- goBack();
162
- return true;
163
- }
164
-
165
- // Try step back
166
- const step = state.currentStep ?? 0;
167
- if (step > 0) {
168
- updateState({
169
- currentStep: step - 1,
170
- screenProps: { ...state.screenProps, initialStep: step - 1 },
171
- });
172
- return true;
173
- }
174
-
175
- // Close
176
- closeBottomSheet();
177
- return true;
178
- }, []);
179
-
180
- const canDismiss = useCallback((): boolean => {
181
- const state = getState();
182
- if (state.history.length > 0) return false;
183
- const step = state.currentStep ?? 0;
184
- return step <= 0;
185
- }, []);
186
-
187
- const handleDismissAttempt = useCallback((): boolean => {
188
- if (!canDismiss()) {
189
- handleGoBack();
190
- return false;
191
- }
192
- return true;
193
- }, [canDismiss, handleGoBack]);
194
-
195
- const handleDismiss = useCallback(() => {
196
- closeBottomSheet();
197
- onDismiss?.();
198
- }, [onDismiss]);
199
-
200
- const handleStepChange = useCallback((step: number) => {
201
- const state = getState();
202
- updateState({
203
- currentStep: step,
204
- screenProps: { ...state.screenProps, initialStep: step },
205
- });
206
- }, []);
207
-
208
- const scrollTo = useCallback((y: number, animated?: boolean) => {
209
- sheetRef.current?.scrollTo(y, animated);
210
- }, []);
211
-
212
- const renderBackground = useCallback(
213
- (props: { style?: StyleProp<ViewStyle> }) => (
214
- <View style={[styles.background, { backgroundColor: theme.colors.background }, props.style]} />
215
- ),
216
- [theme.colors.background]
217
- );
218
-
219
- const screenPropsValue = useMemo((): BaseScreenProps & { scrollTo: typeof scrollTo } => {
220
- const { initialStep: _, ...rest } = screenProps;
221
- return {
222
- navigate,
223
- goBack: handleGoBack,
224
- onClose: closeBottomSheet,
225
- onAuthenticated: closeBottomSheet,
226
- theme: theme.mode,
227
- currentScreen: currentScreen ?? undefined,
228
- initialStep: currentStep ?? (screenProps?.initialStep as number | undefined),
229
- onStepChange: handleStepChange,
230
- scrollTo,
231
- ...rest,
232
- };
233
- }, [navigate, handleGoBack, theme.mode, currentScreen, currentStep, screenProps, handleStepChange, scrollTo]);
234
-
235
- // The active screen, wrapped in its error boundary. Routed into EXACTLY ONE
236
- // surface (the in-tree sheet OR the Dialog) based on the resolved
237
- // presentation, so it never double-mounts.
238
- const screenNode =
239
- ScreenComponent && currentScreen ? (
240
- <ScreenErrorBoundary screenName={currentScreen}>
241
- <ScreenComponent {...screenPropsValue} />
242
- </ScreenErrorBoundary>
243
- ) : null;
244
-
245
- return (
246
- <>
247
- <BottomSheet
248
- ref={sheetRef}
249
- enablePanDownToClose
250
- enableHandlePanningGesture
251
- backgroundComponent={renderBackground}
252
- style={styles.container}
253
- onDismiss={handleDismiss}
254
- onDismissAttempt={handleDismissAttempt}
255
- scrollable={sheetConfig.scrollable}
256
- manualActivation={sheetConfig.manualActivation}
257
- dynamicBackdrop={sheetConfig.dynamicBackdrop}
258
- handleComponent={sheetConfig.handleComponent}
259
- >
260
- {!isDialogPresentation && screenNode}
261
- </BottomSheet>
262
- {/* Always-mounted responsive Dialog surface for `presentation:
263
- 'dialog'` routes (currently the flagship photo picker). It stays
264
- inert (`open={false}`, no child) for the ~28 sheet routes, so they
265
- keep byte-for-byte identical behavior. `contentPadding={0}` +
266
- the full-bleed black, rounded, clipped surface let the picker
267
- paint edge-to-edge; `dismissOnBackdrop={false}` keeps the
268
- controlled open state authoritative (dismissal flows through the
269
- store, matching OxyAccountDialog). */}
270
- <Dialog
271
- open={isOpen && isDialogPresentation}
272
- onClose={handleDismiss}
273
- placement={DIALOG_PLACEMENT}
274
- dismissOnBackdrop={false}
275
- contentPadding={0}
276
- maxWidth={640}
277
- maxHeightRatio={0.9}
278
- style={styles.dialogSurface}
279
- panelStyle={styles.dialogSurface}
280
- label={currentScreen ?? undefined}
281
- >
282
- {isDialogPresentation ? screenNode : null}
283
- </Dialog>
284
- </>
285
- );
286
- };
287
-
288
- const styles = StyleSheet.create({
289
- container: {
290
- maxWidth: 800,
291
- width: '100%',
292
- alignSelf: 'center',
293
- marginHorizontal: 'auto',
294
- },
295
- background: {
296
- borderTopLeftRadius: 20,
297
- borderTopRightRadius: 20,
298
- overflow: 'hidden',
299
- },
300
- // Full-bleed black surface for `presentation: 'dialog'` routes. `overflow:
301
- // 'hidden'` clips the picker's edge-to-edge black content to the panel's
302
- // rounded corners (Dialog supplies the radius per placement); the black
303
- // fill covers any sub-pixel gap behind the picker during entrance.
304
- dialogSurface: {
305
- backgroundColor: '#000000',
306
- overflow: 'hidden',
307
- },
308
- });
309
-
310
- export default React.memo(BottomSheetRouter);
@@ -1,36 +0,0 @@
1
- import { getSheetConfig } from '../routes';
2
-
3
- /**
4
- * `getSheetConfig` drives which surface `BottomSheetRouter` mounts a route in.
5
- * The flagship image picker (FileManagement in image-only select mode) resolves
6
- * to `presentation: 'dialog'`; every other route stays `'sheet'`. These guard
7
- * the routing contract so the ~28 sheet screens keep the in-tree BottomSheet.
8
- */
9
- describe('getSheetConfig presentation', () => {
10
- const imageOnlyPickerProps = {
11
- selectMode: true,
12
- disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
13
- };
14
-
15
- it('defaults every route to the in-tree sheet', () => {
16
- expect(getSheetConfig('ManageAccount', {}).presentation).toBe('sheet');
17
- expect(getSheetConfig('AvatarCrop', {}).presentation).toBe('sheet');
18
- expect(getSheetConfig(null, {}).presentation).toBe('sheet');
19
- });
20
-
21
- it('routes the FileManagement image-only picker to the Dialog', () => {
22
- expect(getSheetConfig('FileManagement', imageOnlyPickerProps).presentation).toBe('dialog');
23
- });
24
-
25
- it('keeps FileManagement on the sheet when it is not an image-only picker', () => {
26
- // Browse mode (no selectMode) — the standard file manager.
27
- expect(getSheetConfig('FileManagement', {}).presentation).toBe('sheet');
28
- // Select mode that still allows non-image types is not the flagship picker.
29
- expect(
30
- getSheetConfig('FileManagement', {
31
- selectMode: true,
32
- disabledMimeTypes: ['video/'],
33
- }).presentation,
34
- ).toBe('sheet');
35
- });
36
- });