@oxyhq/services 22.8.4 → 22.8.6

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 (51) hide show
  1. package/lib/commonjs/ui/components/BottomSheetRouter.js +68 -23
  2. package/lib/commonjs/ui/components/BottomSheetRouter.js.map +1 -1
  3. package/lib/commonjs/ui/components/ProfileButton.js +4 -10
  4. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js +1 -1
  6. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
  7. package/lib/commonjs/ui/navigation/routes.js +7 -4
  8. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  9. package/lib/commonjs/ui/screens/FileManagementScreen.js +49 -173
  10. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  11. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js +3 -0
  12. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js.map +1 -1
  13. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +111 -217
  14. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  15. package/lib/module/ui/components/BottomSheetRouter.js +69 -24
  16. package/lib/module/ui/components/BottomSheetRouter.js.map +1 -1
  17. package/lib/module/ui/components/ProfileButton.js +3 -10
  18. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  19. package/lib/module/ui/components/fileManagement/UploadPreview.js +1 -1
  20. package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
  21. package/lib/module/ui/navigation/routes.js +7 -4
  22. package/lib/module/ui/navigation/routes.js.map +1 -1
  23. package/lib/module/ui/screens/FileManagementScreen.js +50 -174
  24. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  25. package/lib/module/ui/screens/fileManagement/FileListSection.js +3 -0
  26. package/lib/module/ui/screens/fileManagement/FileListSection.js.map +1 -1
  27. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +112 -218
  28. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  29. package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +12 -0
  32. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  36. package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +1 -1
  37. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  38. package/lib/typescript/module/ui/navigation/routes.d.ts +12 -0
  39. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  40. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  41. package/lib/typescript/module/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  42. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/ui/components/BottomSheetRouter.tsx +81 -28
  45. package/src/ui/components/ProfileButton.tsx +2 -14
  46. package/src/ui/components/fileManagement/UploadPreview.tsx +1 -1
  47. package/src/ui/navigation/__tests__/getSheetConfig.test.ts +36 -0
  48. package/src/ui/navigation/routes.ts +19 -4
  49. package/src/ui/screens/FileManagementScreen.tsx +36 -174
  50. package/src/ui/screens/fileManagement/FileListSection.tsx +1 -0
  51. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +127 -223
@@ -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,EAKH,KAAK,SAAS,EACd,KAAK,SAAS,EACjB,MAAM,cAAc,CAAC;AAuCtB,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B;;;;OAIG;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAwP/C,CAAC;AAqDF,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;AAwCtB,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B;;;;OAIG;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+O/C,CAAC;AAiDF,eAAe,aAAa,CAAC"}
@@ -9,11 +9,23 @@ export declare const isValidRoute: (routeName: string) => routeName is RouteName
9
9
  * remove fields without bumping the consumer surface as a breaking change.
10
10
  */
11
11
  export interface SheetRouteConfig {
12
+ /**
13
+ * Which surface hosts the route. `'sheet'` (default) renders the screen in
14
+ * the in-tree `BottomSheet`. `'dialog'` renders it in a responsive Bloom
15
+ * `<Dialog>` (bottom-sheet on narrow, centered card on `md+`) — used for the
16
+ * flagship image picker, which reads better as a centered panel on wide
17
+ * viewports than a full-width sheet. `BottomSheetRouter` keeps BOTH surfaces
18
+ * mounted and routes the active screen into exactly one based on this value.
19
+ */
20
+ presentation: 'sheet' | 'dialog';
12
21
  /**
13
22
  * When `false`, BottomSheet skips its internal ScrollView and lets the
14
23
  * screen own scrolling. Required for screens that render a FlatList,
15
24
  * SectionList, or any other VirtualizedList — nesting one inside a
16
25
  * plain ScrollView breaks windowing and triggers a RN warning.
26
+ *
27
+ * Ignored for `presentation: 'dialog'` routes — those don't render in the
28
+ * in-tree `BottomSheet` at all.
17
29
  */
18
30
  scrollable: boolean;
19
31
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAO3D,MAAM,MAAM,SAAS,GACf,eAAe,GACf,qBAAqB,GACrB,gBAAgB,GAChB,SAAS,GACT,kBAAkB,GAClB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,KAAK,GACL,UAAU,GACV,gBAAgB,GAChB,SAAS,GACT,qBAAqB,GACrB,gBAAgB,GAChB,WAAW,GACX,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,kBAAkB,GAClB,cAAc,GACd,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,eAAe,GACf,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,YAAY,GACZ,eAAe,GACf,eAAe,GACf,aAAa,CAAC;AAkDpB,eAAO,MAAM,kBAAkB,GAAI,WAAW,SAAS,KAAG,aAAa,CAAC,eAAe,CAAC,GAAG,SAsB1F,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,SAAS,IAAI,SAE7D,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;;OAKG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,SAAS,CAAC;CACrC;AA+BD;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACvB,WAAW,SAAS,GAAG,IAAI,EAC3B,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACrC,gBASF,CAAC"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAO3D,MAAM,MAAM,SAAS,GACf,eAAe,GACf,qBAAqB,GACrB,gBAAgB,GAChB,SAAS,GACT,kBAAkB,GAClB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,KAAK,GACL,UAAU,GACV,gBAAgB,GAChB,SAAS,GACT,qBAAqB,GACrB,gBAAgB,GAChB,WAAW,GACX,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,kBAAkB,GAClB,cAAc,GACd,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,eAAe,GACf,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,YAAY,GACZ,eAAe,GACf,eAAe,GACf,aAAa,CAAC;AAkDpB,eAAO,MAAM,kBAAkB,GAAI,WAAW,SAAS,KAAG,aAAa,CAAC,eAAe,CAAC,GAAG,SAsB1F,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,SAAS,IAAI,SAE7D,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;;;;OAOG;IACH,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAC;IACjC;;;;;;;;OAQG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,SAAS,CAAC;CACrC;AAgCD;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACvB,WAAW,SAAS,GAAG,IAAI,EAC3B,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACrC,gBAWF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAkBjF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AA2CzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAmoD7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAejF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AA4CzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA2/C7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileListSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/FileListSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAIH,UAAU,EAGb,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACjC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnD,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;IACnE,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CAC3C;AAED;;;;GAIG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4EnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"FileListSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/FileListSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAIH,UAAU,EAGb,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACjC,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAClD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnD,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;IACnE,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CAC3C;AAED;;;;GAIG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA6EnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"PhotoPickerSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/PhotoPickerSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAejF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6FhD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAsRD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAiWnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"PhotoPickerSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/PhotoPickerSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAgBjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2FhD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAmLD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAqWnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "22.8.4",
3
+ "version": "22.8.6",
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",
@@ -5,6 +5,7 @@ import type { RouteName } from '../navigation/routes';
5
5
  import { getScreenComponent, getSheetConfig, isValidRoute } from '../navigation/routes';
6
6
  import type { BaseScreenProps } from '../types/navigation';
7
7
  import { useTheme } from '@oxyhq/bloom/theme';
8
+ import { Dialog } from '@oxyhq/bloom/dialog';
8
9
  import BottomSheet, { type BottomSheetRef } from './BottomSheet';
9
10
  import {
10
11
  bottomSheetStore,
@@ -67,6 +68,15 @@ const errorStyles = StyleSheet.create({
67
68
  message: { fontSize: 13, textAlign: 'center' },
68
69
  });
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
+
70
80
  export interface BottomSheetRouterProps {
71
81
  onScreenChange?: (screen: RouteName | null) => void;
72
82
  onDismiss?: () => void;
@@ -93,6 +103,15 @@ const BottomSheetRouter: React.FC<BottomSheetRouterProps> = ({ onScreenChange, o
93
103
  return component ?? null;
94
104
  }, [currentScreen]);
95
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
+
96
115
  // Notify screen changes
97
116
  useEffect(() => {
98
117
  if (prevScreenRef.current !== currentScreen) {
@@ -101,16 +120,19 @@ const BottomSheetRouter: React.FC<BottomSheetRouterProps> = ({ onScreenChange, o
101
120
  }
102
121
  }, [currentScreen, onScreenChange]);
103
122
 
104
- // Control visibility
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).
105
127
  useEffect(() => {
106
128
  if (!sheetRef.current) return;
107
129
 
108
- if (isOpen) {
130
+ if (isOpen && !isDialogPresentation) {
109
131
  sheetRef.current.present();
110
132
  } else {
111
133
  sheetRef.current.dismiss();
112
134
  }
113
- }, [isOpen]);
135
+ }, [isOpen, isDialogPresentation]);
114
136
 
115
137
  // Android back button
116
138
  useEffect(() => {
@@ -210,33 +232,56 @@ const BottomSheetRouter: React.FC<BottomSheetRouterProps> = ({ onScreenChange, o
210
232
  };
211
233
  }, [navigate, handleGoBack, theme.mode, currentScreen, currentStep, screenProps, handleStepChange, scrollTo]);
212
234
 
213
- // Route-level sheet config (e.g. whether the sheet provides its own
214
- // ScrollView or yields scrolling to a VirtualizedList inside the screen).
215
- const sheetConfig = useMemo(
216
- () => getSheetConfig(currentScreen, screenProps),
217
- [currentScreen, screenProps],
218
- );
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;
219
244
 
220
245
  return (
221
- <BottomSheet
222
- ref={sheetRef}
223
- enablePanDownToClose
224
- enableHandlePanningGesture
225
- backgroundComponent={renderBackground}
226
- style={styles.container}
227
- onDismiss={handleDismiss}
228
- onDismissAttempt={handleDismissAttempt}
229
- scrollable={sheetConfig.scrollable}
230
- manualActivation={sheetConfig.manualActivation}
231
- dynamicBackdrop={sheetConfig.dynamicBackdrop}
232
- handleComponent={sheetConfig.handleComponent}
233
- >
234
- {ScreenComponent && currentScreen && (
235
- <ScreenErrorBoundary screenName={currentScreen}>
236
- <ScreenComponent {...screenPropsValue} />
237
- </ScreenErrorBoundary>
238
- )}
239
- </BottomSheet>
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
+ </>
240
285
  );
241
286
  };
242
287
 
@@ -252,6 +297,14 @@ const styles = StyleSheet.create({
252
297
  borderTopRightRadius: 20,
253
298
  overflow: 'hidden',
254
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
+ },
255
308
  });
256
309
 
257
310
  export default React.memo(BottomSheetRouter);
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react-native';
11
11
  import { MaterialCommunityIcons } from '@expo/vector-icons';
12
12
  import { Avatar } from '@oxyhq/bloom/avatar';
13
+ import * as Skeleton from '@oxyhq/bloom/skeleton';
13
14
  import { Text } from '@oxyhq/bloom/typography';
14
15
  import { useTheme } from '@oxyhq/bloom/theme';
15
16
  import { getAccountDisplayName, getAccountFallbackHandle } from '@oxyhq/core';
@@ -140,16 +141,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
140
141
  accessibilityElementsHidden
141
142
  importantForAccessibility="no-hide-descendants"
142
143
  >
143
- <View
144
- style={[
145
- styles.skeletonCircle,
146
- {
147
- width: resolvedAvatarSize,
148
- height: resolvedAvatarSize,
149
- backgroundColor: colors.backgroundSecondary,
150
- },
151
- ]}
152
- />
144
+ <Skeleton.Circle size={resolvedAvatarSize} />
153
145
  </View>
154
146
  );
155
147
  }
@@ -352,10 +344,6 @@ const styles = StyleSheet.create({
352
344
  fullWidth: {
353
345
  width: '100%',
354
346
  },
355
- // Neutral skeleton / signed-out avatar circle (`rounded-full`).
356
- skeletonCircle: {
357
- borderRadius: 9999,
358
- },
359
347
  // Shared trigger row (`flex-row items-center gap-3 rounded-full px-2 py-2`).
360
348
  row: {
361
349
  flexDirection: 'row',
@@ -65,7 +65,7 @@ const UploadPreviewContent: React.FC<{
65
65
  const isImage = pendingFile.type.startsWith('image/');
66
66
  return (
67
67
  <View
68
- key={index}
68
+ key={`${pendingFile.name}-${pendingFile.size}-${index}`}
69
69
  className="bg-secondary border-border flex-row items-center p-[12px] rounded-[12px] border mb-[12px] gap-[12px]"
70
70
  >
71
71
  {isImage && pendingFile.preview ? (
@@ -0,0 +1,36 @@
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
+ });
@@ -128,11 +128,23 @@ export const isValidRoute = (routeName: string): routeName is RouteName => {
128
128
  * remove fields without bumping the consumer surface as a breaking change.
129
129
  */
130
130
  export interface SheetRouteConfig {
131
+ /**
132
+ * Which surface hosts the route. `'sheet'` (default) renders the screen in
133
+ * the in-tree `BottomSheet`. `'dialog'` renders it in a responsive Bloom
134
+ * `<Dialog>` (bottom-sheet on narrow, centered card on `md+`) — used for the
135
+ * flagship image picker, which reads better as a centered panel on wide
136
+ * viewports than a full-width sheet. `BottomSheetRouter` keeps BOTH surfaces
137
+ * mounted and routes the active screen into exactly one based on this value.
138
+ */
139
+ presentation: 'sheet' | 'dialog';
131
140
  /**
132
141
  * When `false`, BottomSheet skips its internal ScrollView and lets the
133
142
  * screen own scrolling. Required for screens that render a FlatList,
134
143
  * SectionList, or any other VirtualizedList — nesting one inside a
135
144
  * plain ScrollView breaks windowing and triggers a RN warning.
145
+ *
146
+ * Ignored for `presentation: 'dialog'` routes — those don't render in the
147
+ * in-tree `BottomSheet` at all.
136
148
  */
137
149
  scrollable: boolean;
138
150
  /**
@@ -187,6 +199,7 @@ const isFileManagementImageOnlyPicker = (props: Record<string, unknown>): boolea
187
199
 
188
200
  /** Defaults shared across all routes — preserves the historical in-tree BS UX. */
189
201
  const DEFAULT_SHEET_CONFIG: SheetRouteConfig = {
202
+ presentation: 'sheet',
190
203
  scrollable: true,
191
204
  manualActivation: true,
192
205
  dynamicBackdrop: true,
@@ -203,10 +216,12 @@ export const getSheetConfig = (
203
216
  ): SheetRouteConfig => {
204
217
  if (!routeName) return DEFAULT_SHEET_CONFIG;
205
218
  if (routeName === 'FileManagement' && isFileManagementImageOnlyPicker(screenProps)) {
206
- // PhotoPickerView owns its own FlatList the sheet must not wrap a
207
- // ScrollView around it. Everything else stays at the standard
208
- // manualActivation + dynamicBackdrop defaults.
209
- return { ...DEFAULT_SHEET_CONFIG, scrollable: false };
219
+ // The flagship photo picker renders in a responsive Bloom `<Dialog>`
220
+ // (bottom-sheet on narrow, centered card on `md+`) rather than the
221
+ // in-tree BottomSheet it owns its own FlatList and reads better as a
222
+ // centered panel on wide viewports. The Dialog surface, not this
223
+ // config's `scrollable`, governs its scrolling.
224
+ return { ...DEFAULT_SHEET_CONFIG, presentation: 'dialog' };
210
225
  }
211
226
  return DEFAULT_SHEET_CONFIG;
212
227
  };
@@ -9,15 +9,13 @@ import {
9
9
  RefreshControl,
10
10
  TextInput,
11
11
  Image,
12
- Animated,
13
- Easing,
14
12
  AccessibilityInfo,
15
- Platform,
16
13
  useWindowDimensions,
17
14
  } from 'react-native';
18
15
  import { Image as ExpoImage } from 'expo-image';
19
16
  import type { FileManagementScreenProps } from '../types/fileManagement';
20
17
  import { Dialog, toast, useDialogControl } from '@oxyhq/bloom';
18
+ import * as Skeleton from '@oxyhq/bloom/skeleton';
21
19
  import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
22
20
  import type { FileMetadata } from '@oxyhq/core';
23
21
  import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
@@ -1121,193 +1119,57 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1121
1119
  </View>
1122
1120
  );
1123
1121
 
1124
- // Professional Skeleton Loading Component with Advanced Shimmer Effect
1125
- const SkeletonLoader = React.memo(() => {
1126
- const shimmerAnim = useRef(new Animated.Value(0)).current;
1127
- const skeletonContainerWidth = safeContainerWidth;
1128
-
1129
- useEffect(() => {
1130
- const shimmer = Animated.loop(
1131
- Animated.timing(shimmerAnim, {
1132
- toValue: 1,
1133
- duration: 2000,
1134
- easing: Easing.linear,
1135
- // Web has no native animated module; forcing the native
1136
- // driver there warns and falls back to JS. The shimmer only
1137
- // drives a transform, so JS-driving it on web is fine.
1138
- useNativeDriver: Platform.OS !== 'web',
1139
- })
1140
- );
1141
- shimmer.start();
1142
- return () => shimmer.stop();
1143
- }, [shimmerAnim]);
1144
-
1145
- // Create a sweeping shimmer effect
1146
- const shimmerTranslateX = shimmerAnim.interpolate({
1147
- inputRange: [0, 1],
1148
- outputRange: [-skeletonContainerWidth * 2, skeletonContainerWidth * 2],
1149
- });
1150
-
1151
- const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?: Record<string, unknown>; delay?: number }) => {
1152
- const delayedTranslateX = shimmerAnim.interpolate({
1153
- inputRange: [0, 1],
1154
- outputRange: [-skeletonContainerWidth * 2 + delay, skeletonContainerWidth * 2 + delay],
1155
- });
1156
-
1157
- return (
1158
- <View
1159
- style={[
1160
- {
1161
- width,
1162
- height,
1163
- borderRadius,
1164
- backgroundColor: colors.backgroundSecondary,
1165
- overflow: 'hidden',
1166
- position: 'relative',
1167
- },
1168
- style,
1169
- ]}
1170
- >
1171
- {/* Base background */}
1172
- <View
1173
- style={{
1174
- position: 'absolute',
1175
- top: 0,
1176
- left: 0,
1177
- right: 0,
1178
- bottom: 0,
1179
- backgroundColor: colors.backgroundSecondary,
1180
- }}
1181
- />
1182
- {/* Shimmer gradient effect */}
1183
- <Animated.View
1184
- style={{
1185
- position: 'absolute',
1186
- top: 0,
1187
- left: 0,
1188
- width: '100%',
1189
- height: '100%',
1190
- transform: [{ translateX: delayedTranslateX }],
1191
- }}
1192
- >
1193
- <View
1194
- style={{
1195
- width: skeletonContainerWidth,
1196
- height: '100%',
1197
- backgroundColor: bloomTheme.isDark
1198
- ? 'rgba(255, 255, 255, 0.08)'
1199
- : 'rgba(255, 255, 255, 0.8)',
1200
- shadowColor: bloomTheme.isDark ? '#000' : '#FFF',
1201
- shadowOffset: { width: 0, height: 0 },
1202
- shadowOpacity: 0.3,
1203
- shadowRadius: 10,
1204
- }}
1205
- />
1206
- </Animated.View>
1207
- </View>
1208
- );
1209
- };
1210
-
1211
- // Skeleton file item matching SettingsListItem structure
1212
- const SkeletonFileItem = ({ index }: { index: number }) => (
1213
- <View
1214
- style={[
1215
- {
1216
- flexDirection: 'row',
1217
- alignItems: 'center',
1218
- paddingHorizontal: 16,
1219
- paddingVertical: 12,
1220
- backgroundColor: colors.background,
1221
- borderBottomWidth: StyleSheet.hairlineWidth,
1222
- borderBottomColor: colors.border,
1223
- },
1224
- ]}
1225
- >
1226
- {/* Icon/Image skeleton */}
1227
- <SkeletonBox width={44} height={44} borderRadius={8} delay={index * 50} />
1228
-
1229
- {/* Content skeleton */}
1230
- <View style={{ flex: 1, marginLeft: 12, justifyContent: 'center' }}>
1231
- <SkeletonBox
1232
- width={index % 3 === 0 ? '85%' : index % 3 === 1 ? '70%' : '90%'}
1233
- height={16}
1234
- style={{ marginBottom: 8 }}
1235
- delay={index * 50 + 20}
1236
- />
1237
- <SkeletonBox
1238
- width={index % 2 === 0 ? '50%' : '60%'}
1239
- height={12}
1240
- delay={index * 50 + 40}
1241
- />
1242
- </View>
1243
- </View>
1122
+ // Loading state — Bloom's shared `Skeleton` primitives own the shimmer, so
1123
+ // this is pure layout: the top chrome (header / controls / search) plus a
1124
+ // photo grid of shimmering tiles sized with the same 3-per-row geometry the
1125
+ // real photo grid uses. No hand-rolled `Animated` shimmer.
1126
+ if (loading) {
1127
+ const GRID_PADDING = 10;
1128
+ const TILE_GAP = 4;
1129
+ const GRID_COLUMNS = 3;
1130
+ const GRID_ROWS = 6;
1131
+ const tileSize = Math.floor(
1132
+ (safeContainerWidth - GRID_PADDING * 2 - TILE_GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS,
1244
1133
  );
1245
1134
 
1246
1135
  return (
1247
1136
  <View className="flex-1" style={{ backgroundColor }}>
1248
- {/* Header Skeleton */}
1137
+ {/* Header */}
1249
1138
  <View
1250
- className="flex-row items-center justify-between px-[16px] py-[12px] relative"
1139
+ className="flex-row items-center justify-between px-[16px] py-[12px]"
1251
1140
  style={{ borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}
1252
1141
  >
1253
- <SkeletonBox width={44} height={44} borderRadius={12} />
1254
- <View className="flex-1 items-center justify-center mx-[16px]">
1255
- <SkeletonBox width={140} height={20} style={{ marginBottom: 6 }} />
1256
- <SkeletonBox width={100} height={14} />
1257
- </View>
1258
- <SkeletonBox width={44} height={44} borderRadius={12} />
1142
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1143
+ <Skeleton.Col style={{ alignItems: 'center', marginHorizontal: 16, gap: 6 }}>
1144
+ <Skeleton.Box width={140} height={20} />
1145
+ <Skeleton.Box width={100} height={14} />
1146
+ </Skeleton.Col>
1147
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1259
1148
  </View>
1260
1149
 
1261
- {/* Controls Bar Skeleton */}
1262
- <View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
1263
- <SkeletonBox width={100} height={36} borderRadius={18} />
1264
- <SkeletonBox width={44} height={44} borderRadius={22} />
1265
- </View>
1150
+ {/* Controls bar */}
1151
+ <Skeleton.Row style={{ alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12 }}>
1152
+ <Skeleton.Box width={100} height={36} borderRadius={18} />
1153
+ <Skeleton.Box width={44} height={44} borderRadius={22} />
1154
+ </Skeleton.Row>
1266
1155
 
1267
- {/* Search Bar Skeleton */}
1268
- <View
1269
- className="flex-row items-center px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-full gap-[10px]"
1270
- style={{ backgroundColor: colors.card }}
1271
- >
1272
- <SkeletonBox width="100%" height={44} borderRadius={12} />
1156
+ {/* Search bar */}
1157
+ <View className="mx-[12px] mb-[12px]">
1158
+ <Skeleton.Box width="100%" height={44} borderRadius={22} />
1273
1159
  </View>
1274
1160
 
1275
- {/* Stats Container Skeleton */}
1276
- <View
1277
- className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
1278
- style={{ backgroundColor: colors.card }}
1279
- >
1280
- {[1, 2, 3].map((i) => (
1281
- <View key={i} className="flex-1 items-center py-[4px]">
1282
- <SkeletonBox width={50} height={20} style={{ marginBottom: 4 }} delay={i * 30} />
1283
- <SkeletonBox width={40} height={14} delay={i * 30 + 15} />
1284
- </View>
1161
+ {/* Photo grid */}
1162
+ <View style={{ padding: GRID_PADDING, gap: TILE_GAP }}>
1163
+ {Array.from({ length: GRID_ROWS }, (_, row) => (
1164
+ <Skeleton.Row key={row} style={{ gap: TILE_GAP }}>
1165
+ {Array.from({ length: GRID_COLUMNS }, (_, col) => (
1166
+ <Skeleton.Box key={col} width={tileSize} height={tileSize} borderRadius={6} />
1167
+ ))}
1168
+ </Skeleton.Row>
1285
1169
  ))}
1286
1170
  </View>
1287
-
1288
- {/* File List Skeleton - Matching SettingsListItem */}
1289
- <ScrollView
1290
- className="flex-1"
1291
- contentContainerClassName="px-[12px] pt-0 pb-[12px]"
1292
- showsVerticalScrollIndicator={false}
1293
- >
1294
- <View style={{
1295
- backgroundColor: colors.card,
1296
- borderRadius: 18,
1297
- overflow: 'hidden',
1298
- marginTop: 8,
1299
- }}>
1300
- {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
1301
- <SkeletonFileItem key={i} index={i} />
1302
- ))}
1303
- </View>
1304
- </ScrollView>
1305
1171
  </View>
1306
1172
  );
1307
- });
1308
-
1309
- if (loading) {
1310
- return <SkeletonLoader />;
1311
1173
  }
1312
1174
 
1313
1175
  // Dedicated flagship-style photo picker view used in the avatar-picker
@@ -63,6 +63,7 @@ const FileListSection: React.FC<FileListSectionProps> = ({
63
63
  <ScrollView
64
64
  ref={scrollViewRef}
65
65
  className="flex-1"
66
+ style={{ flex: 1 }}
66
67
  contentContainerClassName="px-space-12 pt-0 pb-space-12"
67
68
  refreshControl={
68
69
  <RefreshControl