@oxyhq/services 22.12.0 → 22.12.1

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 (24) hide show
  1. package/lib/commonjs/ui/hooks/useSurfaceHeader.js +2 -2
  2. package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
  3. package/lib/commonjs/ui/screens/FileManagementScreen.js +11 -5
  4. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  5. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +35 -41
  6. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  7. package/lib/module/ui/hooks/useSurfaceHeader.js +2 -2
  8. package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
  9. package/lib/module/ui/screens/FileManagementScreen.js +11 -5
  10. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  11. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +35 -41
  12. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  13. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  14. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts +1 -1
  15. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  16. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  17. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  18. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts +1 -1
  19. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  20. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/src/ui/hooks/useSurfaceHeader.ts +2 -17
  23. package/src/ui/screens/FileManagementScreen.tsx +7 -4
  24. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +35 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "22.12.0",
3
+ "version": "22.12.1",
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",
@@ -176,7 +176,7 @@
176
176
  },
177
177
  "peerDependencies": {
178
178
  "@expo/vector-icons": "^15.0.3",
179
- "@oxyhq/bloom": "^0.49.1",
179
+ "@oxyhq/bloom": "^0.50.0",
180
180
  "@oxyhq/core": "^12.10.0",
181
181
  "@react-native-async-storage/async-storage": "^2.0.0",
182
182
  "@react-native-community/netinfo": "^11.4.1",
@@ -57,21 +57,6 @@ export function useSurfaceHeader(content: SurfaceHeaderContent | null | undefine
57
57
  if (!set) return;
58
58
  set(content ?? null);
59
59
  return () => set(null);
60
- // Slot nodes (`left`/`right`) are compared by identity; callers memoize them.
61
- }, [
62
- set,
63
- content?.title,
64
- content?.titleContent,
65
- content?.subtitle,
66
- content?.largeTitle,
67
- content?.left,
68
- content?.right,
69
- content?.onBack,
70
- content?.primaryAction,
71
- content?.actions,
72
- content?.search,
73
- content?.segments,
74
- content?.tone,
75
- content?.progress,
76
- ]);
60
+ // Callers must memoize `content` (especially slot nodes) so the header does not thrash.
61
+ }, [set, content]);
77
62
  }
@@ -1,4 +1,5 @@
1
- import React, { useState, useCallback, useMemo, useRef } from 'react';
1
+ import type React from 'react';
2
+ import { useState, useCallback, useMemo, useRef } from 'react';
2
3
  import {
3
4
  View,
4
5
  Text,
@@ -1100,12 +1101,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1100
1101
  <Skeleton.Box width="100%" height={44} borderRadius={22} />
1101
1102
  </View>
1102
1103
 
1103
- {/* Photo grid */}
1104
+ {/* Photo grid — static skeleton placeholders; index keys are stable */}
1104
1105
  <View style={{ padding: GRID_PADDING, gap: TILE_GAP }}>
1105
1106
  {Array.from({ length: GRID_ROWS }, (_, row) => (
1106
- <Skeleton.Row key={row} style={{ gap: TILE_GAP }}>
1107
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1108
+ <Skeleton.Row key={`skeleton-row-${row}`} style={{ gap: TILE_GAP }}>
1107
1109
  {Array.from({ length: GRID_COLUMNS }, (_, col) => (
1108
- <Skeleton.Box key={col} width={tileSize} height={tileSize} borderRadius={6} />
1110
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1111
+ <Skeleton.Box key={`skeleton-${row}-${col}`} width={tileSize} height={tileSize} borderRadius={6} />
1109
1112
  ))}
1110
1113
  </Skeleton.Row>
1111
1114
  ))}
@@ -74,26 +74,22 @@ const hapticSelection = async (): Promise<void> => {
74
74
  };
75
75
 
76
76
  /**
77
- * The picker renders inside a Bloom `<Dialog>` in NAV-HEADER mode (bottom-sheet on
78
- * narrow, centered card on `md+`). The Dialog owns the shared gradient nav bar
79
- * (Cancel / "Choose Photo" / Upload, wired by `FileManagementScreen` through
80
- * `useSurfaceHeader`) the picker no longer paints a bar of its own. In this mode
81
- * the Dialog reserves a fixed nav-bar-height SPACER above the picker's content and
82
- * its gradient overlay fades a little further into the content; the grid's own
83
- * top padding just clears that fade so the first row reads cleanly. Two fixed
84
- * layout constants mirror Bloom internals:
85
- * - `DIALOG_NAV_BAR_SPACER` mirrors Bloom's `DIALOG_NAV_BAR_HEIGHT` (not exported
86
- * from `@oxyhq/bloom/dialog`) the spacer the Dialog inserts above header-mode
87
- * `scrollable:false` content. The picker's own height is reduced by it so the
88
- * spacer + grid together fill the `frameSize` morph target (and never overflow
89
- * the panel's `maxHeightRatio: 0.9` cap, which would clip the last row).
90
- * - `HEADER_GRADIENT_FADE` is the gradient tail that bleeds past the bar into the
91
- * content; the grid's `pt-[24px]` (fade + 4dp breathing room) clears it. The
92
- * `GRID_CONTENT_PADDING_TOP` constant MUST stay in sync with that class.
77
+ * The picker renders inside a Bloom `<Dialog>` in NAV-HEADER mode with
78
+ * `tone: 'onImage'` (bottom-sheet on narrow, centered card on `md+`). The Dialog
79
+ * owns the shared gradient nav bar (Cancel / "Choose Photo" / Upload, wired by
80
+ * `FileManagementScreen` through `useSurfaceHeader`). Because the tone is
81
+ * `'onImage'`, the Dialog inserts NO nav-bar spacer above this content
82
+ * (`DialogNavBarSpacer` collapses to nothing), so the black photo grid slides UP
83
+ * under the floating translucent bar and reads EDGE-TO-EDGE the Apple/Google
84
+ * Photos immersive picker. The grid therefore takes the FULL `frameSize` height
85
+ * and carries no top padding; only the pull-to-refresh spinner is offset so it
86
+ * lands below the floating bar.
87
+ *
88
+ * `FLOATING_NAV_BAR_HEIGHT` mirrors Bloom's `DIALOG_NAV_BAR_HEIGHT` (not exported
89
+ * from `@oxyhq/bloom/dialog`) the height of the floating bar the refresh spinner
90
+ * must clear.
93
91
  */
94
- const DIALOG_NAV_BAR_SPACER = 52;
95
- const HEADER_GRADIENT_FADE = 20;
96
- const GRID_CONTENT_PADDING_TOP = HEADER_GRADIENT_FADE + 4; // 24 — matches `pt-[24px]`
92
+ const FLOATING_NAV_BAR_HEIGHT = 52;
97
93
 
98
94
  /**
99
95
  * Props for the dedicated photo picker view. Used by FileManagementScreen
@@ -383,18 +379,15 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
383
379
 
384
380
  // The picker FILLS its panel: the surface morphs the container UP to an
385
381
  // explicit large target (`frameSize.heightRatio: 0.9` in `surfaceRegistry`),
386
- // and this root takes that SAME 0.9-of-viewport height MINUS the Dialog's
387
- // nav-bar spacer (which the Dialog inserts ABOVE this content in header mode).
388
- // spacer + root then exactly fill the morph target and stay within the panel's
389
- // `maxHeightRatio: 0.9` cap without the subtraction the grid's last row would
390
- // clip. A `flex: 1` root can't fill (no definite bound propagates through the
391
- // sheet's flex chain at rest, where the panel is height:auto), so the fixed
392
- // height is required; the FlatList scrolls within it. Keep the 0.9 ratio in
393
- // sync with the picker's `frameSize` in `surfaceRegistry.ts`.
394
- const panelHeight = useMemo(
395
- () => Math.round(windowHeight * 0.9) - DIALOG_NAV_BAR_SPACER,
396
- [windowHeight],
397
- );
382
+ // and this root takes that SAME 0.9-of-viewport height. Under `tone:'onImage'`
383
+ // the Dialog inserts NO nav-bar spacer, so the grid uses the FULL morph target
384
+ // (no `-52`) and slides up under the floating bar, edge-to-edge, within the
385
+ // panel's `maxHeightRatio: 0.9` cap. A `flex: 1` root can't fill (no definite
386
+ // bound propagates through the sheet's flex chain at rest, where the panel is
387
+ // height:auto), so the fixed height is required; the FlatList scrolls within
388
+ // it. Keep the 0.9 ratio in sync with the picker's `frameSize` in
389
+ // `surfaceRegistry.ts`.
390
+ const panelHeight = useMemo(() => Math.round(windowHeight * 0.9), [windowHeight]);
398
391
  const rootStyle = useMemo(() => ({ height: panelHeight }), [panelHeight]);
399
392
 
400
393
  const effectiveWidth = gridWidth > 0 ? gridWidth : windowWidth;
@@ -410,7 +403,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
410
403
  const skeletonTileCount = useMemo(() => {
411
404
  const rowHeight = cellSize + gutter;
412
405
  if (rowHeight <= 0) return columns * SKELETON_MIN_ROWS;
413
- const available = Math.max(0, panelHeight - GRID_CONTENT_PADDING_TOP);
406
+ const available = Math.max(0, panelHeight - FLOATING_NAV_BAR_HEIGHT);
414
407
  const rows = Math.max(SKELETON_MIN_ROWS, Math.floor(available / rowHeight));
415
408
  return rows * columns;
416
409
  }, [cellSize, gutter, columns, panelHeight]);
@@ -531,9 +524,9 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
531
524
  clears that fade). */}
532
525
  {isEmpty && loadError ? (
533
526
  /* Terminal load failure — a DISTINCT error surface (not the
534
- empty state), cleared below the nav bar's gradient fade.
535
- Retry re-runs the list query. */
536
- <View className="flex-1 items-center justify-center pt-[24px]">
527
+ empty state), centered in the immersive frame. Retry re-runs
528
+ the list query. */
529
+ <View className="flex-1 items-center justify-center">
537
530
  <FileLibraryError
538
531
  title={t('fileManagement.loadError.title')}
539
532
  description={t('fileManagement.loadError.description')}
@@ -548,13 +541,11 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
548
541
  ) : isEmpty && loading ? (
549
542
  /* Loading skeleton — the picker's OWN shape: shimmer tiles laid
550
543
  out with the SAME placement-aware geometry the real grid uses
551
- (`computePhotoGridLayout(effectiveWidth)`), cleared below the
552
- nav bar's gradient fade. `flexWrap` breaks rows exactly at
553
- `columns` because the last tile in each row carries no right
554
- margin (a full row + gutters fits the measured width by
555
- construction). */
544
+ (`computePhotoGridLayout(effectiveWidth)`), edge-to-edge under
545
+ the floating bar. `flexWrap` breaks rows exactly at `columns`
546
+ because the last tile in each row carries no right margin (a
547
+ full row + gutters fits the measured width by construction). */
556
548
  <View
557
- className="pt-[24px]"
558
549
  style={{ flexDirection: 'row', flexWrap: 'wrap' }}
559
550
  pointerEvents="none"
560
551
  accessibilityElementsHidden
@@ -575,7 +566,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
575
566
  ))}
576
567
  </View>
577
568
  ) : isEmpty ? (
578
- <View className="flex-1 items-center justify-center px-space-32 pt-[24px]">
569
+ <View className="flex-1 items-center justify-center px-space-32">
579
570
  <View className="opacity-30 mb-space-16">
580
571
  <MaterialCommunityIcons name="image-outline" size={64} color="#FFFFFF" />
581
572
  </View>
@@ -616,7 +607,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
616
607
  keyExtractor={keyExtractor}
617
608
  numColumns={columns}
618
609
  className="flex-1 min-h-0"
619
- contentContainerClassName="pt-[24px] pb-space-24"
610
+ contentContainerClassName="pb-space-24"
620
611
  showsVerticalScrollIndicator={false}
621
612
  refreshControl={
622
613
  <RefreshControl
@@ -624,7 +615,7 @@ const PhotoPickerView: React.FC<PhotoPickerViewProps> = ({
624
615
  onRefresh={onRefresh}
625
616
  tintColor="#FFFFFF"
626
617
  colors={[primaryColor]}
627
- progressViewOffset={GRID_CONTENT_PADDING_TOP}
618
+ progressViewOffset={FLOATING_NAV_BAR_HEIGHT}
628
619
  />
629
620
  }
630
621
  onEndReached={handleEndReached}