@oxy.so/bloom 4.1.0 → 4.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.
Files changed (93) hide show
  1. package/docs/adoption-matrix.mdx +7 -7
  2. package/docs/ai-chat.mdx +8 -0
  3. package/docs/app-shell.mdx +7 -0
  4. package/docs/bottom-bar.mdx +6 -0
  5. package/docs/settings-modal.mdx +14 -5
  6. package/docs/sidebar.mdx +11 -0
  7. package/lib/commonjs/ai-chat/AiChatContainer.js +8 -8
  8. package/lib/commonjs/ai-chat/AiChatContainer.js.map +1 -1
  9. package/lib/commonjs/app-shell/AppShellBars.js +8 -2
  10. package/lib/commonjs/app-shell/AppShellBars.js.map +1 -1
  11. package/lib/commonjs/app-shell/AppShellEngine.js +29 -6
  12. package/lib/commonjs/app-shell/AppShellEngine.js.map +1 -1
  13. package/lib/commonjs/bottom-bar/BottomBarBase.js +10 -2
  14. package/lib/commonjs/bottom-bar/BottomBarBase.js.map +1 -1
  15. package/lib/commonjs/layout/bottom-bar-slot.js +10 -0
  16. package/lib/commonjs/layout/bottom-bar-slot.js.map +1 -0
  17. package/lib/commonjs/settings-modal/SettingsModal.js +153 -230
  18. package/lib/commonjs/settings-modal/SettingsModal.js.map +1 -1
  19. package/lib/commonjs/sidebar/Sidebar.js +35 -5
  20. package/lib/commonjs/sidebar/Sidebar.js.map +1 -1
  21. package/lib/module/ai-chat/AiChatContainer.js +8 -8
  22. package/lib/module/ai-chat/AiChatContainer.js.map +1 -1
  23. package/lib/module/app-shell/AppShellBars.js +8 -2
  24. package/lib/module/app-shell/AppShellBars.js.map +1 -1
  25. package/lib/module/app-shell/AppShellEngine.js +30 -7
  26. package/lib/module/app-shell/AppShellEngine.js.map +1 -1
  27. package/lib/module/bottom-bar/BottomBarBase.js +11 -3
  28. package/lib/module/bottom-bar/BottomBarBase.js.map +1 -1
  29. package/lib/module/layout/bottom-bar-slot.js +7 -0
  30. package/lib/module/layout/bottom-bar-slot.js.map +1 -0
  31. package/lib/module/settings-modal/SettingsModal.js +150 -227
  32. package/lib/module/settings-modal/SettingsModal.js.map +1 -1
  33. package/lib/module/sidebar/Sidebar.js +35 -5
  34. package/lib/module/sidebar/Sidebar.js.map +1 -1
  35. package/lib/typescript/commonjs/ai-chat/types.d.ts +2 -2
  36. package/lib/typescript/commonjs/ai-chat/types.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/app-shell/AppShellBars.d.ts +1 -0
  38. package/lib/typescript/commonjs/app-shell/AppShellBars.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/app-shell/AppShellEngine.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/bottom-bar/BottomBarBase.d.ts +1 -1
  41. package/lib/typescript/commonjs/bottom-bar/BottomBarBase.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/bottom-bar/types.d.ts +4 -0
  43. package/lib/typescript/commonjs/bottom-bar/types.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/layout/bottom-bar-slot.d.ts +3 -0
  45. package/lib/typescript/commonjs/layout/bottom-bar-slot.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts +1 -1
  47. package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/settings-modal/types.d.ts +7 -2
  49. package/lib/typescript/commonjs/settings-modal/types.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/sidebar/Sidebar.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/sidebar/types.d.ts +12 -1
  52. package/lib/typescript/commonjs/sidebar/types.d.ts.map +1 -1
  53. package/lib/typescript/module/ai-chat/types.d.ts +2 -2
  54. package/lib/typescript/module/ai-chat/types.d.ts.map +1 -1
  55. package/lib/typescript/module/app-shell/AppShellBars.d.ts +1 -0
  56. package/lib/typescript/module/app-shell/AppShellBars.d.ts.map +1 -1
  57. package/lib/typescript/module/app-shell/AppShellEngine.d.ts.map +1 -1
  58. package/lib/typescript/module/bottom-bar/BottomBarBase.d.ts +1 -1
  59. package/lib/typescript/module/bottom-bar/BottomBarBase.d.ts.map +1 -1
  60. package/lib/typescript/module/bottom-bar/types.d.ts +4 -0
  61. package/lib/typescript/module/bottom-bar/types.d.ts.map +1 -1
  62. package/lib/typescript/module/layout/bottom-bar-slot.d.ts +3 -0
  63. package/lib/typescript/module/layout/bottom-bar-slot.d.ts.map +1 -0
  64. package/lib/typescript/module/settings-modal/SettingsModal.d.ts +1 -1
  65. package/lib/typescript/module/settings-modal/SettingsModal.d.ts.map +1 -1
  66. package/lib/typescript/module/settings-modal/types.d.ts +7 -2
  67. package/lib/typescript/module/settings-modal/types.d.ts.map +1 -1
  68. package/lib/typescript/module/sidebar/Sidebar.d.ts.map +1 -1
  69. package/lib/typescript/module/sidebar/types.d.ts +12 -1
  70. package/lib/typescript/module/sidebar/types.d.ts.map +1 -1
  71. package/package.json +1 -1
  72. package/src/__tests__/support/adoption-matrix.ts +703 -0
  73. package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
  74. package/src/__tests__/support/commerce-harness.tsx +97 -0
  75. package/src/__tests__/support/composite.ts +68 -0
  76. package/src/__tests__/support/constructed-style-sheets.ts +68 -0
  77. package/src/__tests__/support/press-host.ts +30 -0
  78. package/src/__tests__/support/rendered-style.ts +99 -0
  79. package/src/__tests__/support/unread-hook-fixture.ts +33 -0
  80. package/src/ai-chat/AiChatContainer.tsx +7 -7
  81. package/src/ai-chat/types.ts +2 -2
  82. package/src/app-shell/AppShellBars.tsx +5 -3
  83. package/src/app-shell/AppShellEngine.tsx +23 -8
  84. package/src/bottom-bar/BottomBarBase.tsx +7 -4
  85. package/src/bottom-bar/types.ts +4 -0
  86. package/src/layout/bottom-bar-slot.ts +4 -0
  87. package/src/settings-modal/SettingsModal.tsx +180 -250
  88. package/src/settings-modal/types.ts +7 -2
  89. package/src/sidebar/Sidebar.tsx +26 -5
  90. package/src/sidebar/types.ts +12 -1
  91. package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +9346 -0
  92. package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +69 -0
  93. package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
@@ -33,11 +33,11 @@ export interface SettingsNavGroup {
33
33
 
34
34
  /** One page of the content pane. */
35
35
  export interface SettingsModalPage {
36
- /** Heading of the fixed title row. */
36
+ /** Heading of the shared PageHeader. */
37
37
  title: string;
38
38
  content: ReactNode;
39
39
  /**
40
- * Used on the Storage page: a 6px title gap instead of 12px, because the page
40
+ * Used on the Storage page: a 6px gap below PageHeader instead of 12px, because the page
41
41
  * already carries 10px of headroom for the dropzone's progress badge.
42
42
  */
43
43
  compactTitle?: boolean;
@@ -66,6 +66,9 @@ export interface SettingsModalProps {
66
66
  * Imperative mode: the modal finished closing.
67
67
  */
68
68
  onClose?: () => void;
69
+ /** Veto an internal leave intention before it changes or unmounts content.
70
+ * External controlled open/page changes remain the caller's authority. */
71
+ onBeforeLeave?: (reason: 'close' | 'navigation' | 'page') => boolean;
69
72
  groups: SettingsNavGroup[];
70
73
  pages: Record<string, SettingsModalPage>;
71
74
  /** Controlled current page. */
@@ -73,6 +76,8 @@ export interface SettingsModalProps {
73
76
  /** Page selected every time the modal opens. Defaults to the first navigable row. */
74
77
  defaultPage?: string;
75
78
  onPageChange?: (page: string) => void;
79
+ /** Compact layout: start at the section list (default) or open the selected page directly. Resets on each open. */
80
+ initialView?: 'navigation' | 'page';
76
81
  labels?: SettingsModalLabels;
77
82
  testID?: string;
78
83
  }
@@ -141,6 +141,8 @@ function SearchField({
141
141
  const SidebarPanel: React.FC<SidebarProps> = ({
142
142
  items = [],
143
143
  primaryAction,
144
+ content,
145
+ onScroll,
144
146
  footer,
145
147
  secondaryItems = [],
146
148
  modes,
@@ -210,7 +212,7 @@ const SidebarPanel: React.FC<SidebarProps> = ({
210
212
 
211
213
  // `fluid` fills its container while expanded; the morph needs a number, so
212
214
  // the last expanded width is measured. The collapsed end is the size's own.
213
- const geometry = useMemo(() => resolveSidebarGeometry(metrics, surface, Boolean(primaryAction), Boolean(tree)), [metrics, surface, primaryAction, tree]);
215
+ const geometry = useMemo(() => resolveSidebarGeometry(metrics, surface, Boolean(primaryAction), Boolean(tree) || content != null), [metrics, surface, primaryAction, tree, content]);
214
216
  const { collapsedWidth, collapsedLane, expandedInset } = geometry;
215
217
  const expandedWidth = useSharedValue(metrics.expanded);
216
218
  useEffect(() => {
@@ -232,6 +234,13 @@ const SidebarPanel: React.FC<SidebarProps> = ({
232
234
  paddingLeft: padding, paddingRight: padding,
233
235
  };
234
236
  }, [progress, expandedWidth, fluid, collapsedWidth, expandedPadding, collapsedPadding]);
237
+ const [contentHeight, setContentHeight] = useState(0);
238
+ const contentMorph = useAnimatedStyle(() => ({
239
+ height: contentHeight > 0 ? contentHeight * (1 - progress.value) : collapsed ? 0 : undefined,
240
+ opacity: 1 - progress.value,
241
+ marginTop: tree ? 0 : 24 * (1 - progress.value),
242
+ }), [contentHeight, collapsed, progress, tree]);
243
+ const contentWidth = useAnimatedStyle(() => ({ width: expandedWidth.value - expandedPadding * 2 }), [expandedWidth, expandedPadding]);
235
244
  const navInset = useAnimatedStyle(() => {
236
245
  const inset = expandedInset * (1 - progress.value);
237
246
  return { paddingLeft: inset, paddingRight: inset };
@@ -388,7 +397,7 @@ const SidebarPanel: React.FC<SidebarProps> = ({
388
397
  setSuppressUserHover(expanding);
389
398
  };
390
399
 
391
- const headerControl = flatMobile ? (
400
+ const headerControl = flatMobile && showSearch ? (
392
401
  <View
393
402
  ref={searchFieldRef}
394
403
  style={{
@@ -534,8 +543,8 @@ const SidebarPanel: React.FC<SidebarProps> = ({
534
543
  [tree, normalized],
535
544
  );
536
545
  const nothingMatches =
537
- shownItems.length === 0 && shownSecondary.length === 0 && shownFolders.length === 0 && !collapsed;
538
- const hasTree = !!tree;
546
+ shownItems.length === 0 && shownSecondary.length === 0 && shownFolders.length === 0 && content == null && !collapsed;
547
+ const hasTree = !!tree || content != null;
539
548
 
540
549
  // A fresh closure here would re-render the memoised switcher on every
541
550
  // keystroke in the quick-search field.
@@ -642,8 +651,9 @@ const SidebarPanel: React.FC<SidebarProps> = ({
642
651
  testID={`${testID ?? 'sidebar'}-scroll`}
643
652
  {...(IS_WEB ? { dataSet: { bloomSidebarScroll: 'none' } } : {})}
644
653
  style={{ marginTop: -headerOverlap, marginBottom: -8, marginLeft: -8, marginRight: -8, flexGrow: 0, flexShrink: 1, minHeight: 0 }}
645
- contentContainerStyle={{ paddingTop: headerOverlap + 12, paddingBottom: 8, paddingLeft: 8, paddingRight: 8, gap: hasTree ? 24 : 0 }}
654
+ contentContainerStyle={{ paddingTop: headerOverlap + 12, paddingBottom: 8, paddingLeft: 8, paddingRight: 8, gap: tree ? 24 : 0 }}
646
655
  showsVerticalScrollIndicator={false}
656
+ onScroll={onScroll}
647
657
  >
648
658
  {shownItems.length > 0 || !hasTree ? (
649
659
  <Animated.View role="navigation" style={[{ width: '100%', gap: 4 }, !hasTree && navInset]}>
@@ -665,6 +675,17 @@ const SidebarPanel: React.FC<SidebarProps> = ({
665
675
  </View>
666
676
  </View>
667
677
  ) : null}
678
+ {content != null ? (
679
+ <Animated.View testID={`${testID ?? 'sidebar'}-content`} pointerEvents={collapsed ? 'none' : 'auto'}
680
+ aria-hidden={collapsed} accessibilityElementsHidden={collapsed}
681
+ importantForAccessibility={collapsed ? 'no-hide-descendants' : 'auto'}
682
+ {...(IS_WEB && collapsed ? { inert: true } : {})}
683
+ style={[{ overflow: 'hidden', flexShrink: 0 }, contentMorph]}>
684
+ <Animated.View onLayout={(event) => setContentHeight(event.nativeEvent.layout.height)} style={contentWidth}>
685
+ {content}
686
+ </Animated.View>
687
+ </Animated.View>
688
+ ) : null}
668
689
  {nothingMatches && hasTree ? (
669
690
  <Text variant="body-regular" style={{ paddingLeft: 8, paddingRight: 8, color: palette.textTertiary }}>{noResultsLabel}</Text>
670
691
  ) : null}
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { BloomIconComponent } from '../icons/icon-component';
3
- import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { StyleProp, ViewStyle, ScrollViewProps } from 'react-native';
4
4
 
5
5
  /** A Remix-style icon: `width` / `height` / `fill`. */
6
6
  /** @deprecated Use `BloomIconComponent` from `@oxy.so/bloom/icons`; this is an alias of it. */
@@ -232,6 +232,17 @@ export interface SidebarUserMenuProps {
232
232
  }
233
233
 
234
234
  export interface SidebarProps {
235
+ /** App-owned destination content after the built-in rows/tree, inside the same
236
+ * scroll viewport. Hidden and noninteractive while collapsed; remains mounted
237
+ * so pagination and disclosure state survive. Not rendered in variant="rail".
238
+ * Compose rows here, never another ScrollView. Quick search filters only the
239
+ * built-in items/tree; disable it when your content owns search.
240
+ */
241
+ content?: ReactNode;
242
+ /** Observes the destination viewport after Bloom updates its edge fades.
243
+ * Useful for loading another page of history. No separate scroll owner.
244
+ */
245
+ onScroll?: ScrollViewProps['onScroll'];
235
246
  /** Additional persistent bottom content, outside destination scrolling.
236
247
  * Receives the rendered state: true in a rail, false in a mobile panel.
237
248
  * Existing account/theme/team controls remain; omit account when supplying your own account footer.