@oxy.so/bloom 7.1.5 → 7.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.
- package/docs/adoption-matrix.mdx +18 -17
- package/docs/app-shell.mdx +48 -2
- package/docs/appearance.mdx +16 -1
- package/docs/chip.mdx +5 -0
- package/docs/content-panel.mdx +9 -0
- package/docs/error-boundary.mdx +35 -0
- package/docs/mail-thread.mdx +8 -0
- package/docs/page-footer.mdx +60 -0
- package/docs/settings-modal.mdx +5 -0
- package/docs/sidebar.mdx +1 -1
- package/lib/commonjs/app-shell/AppShell.js +3 -2
- package/lib/commonjs/app-shell/AppShell.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellBars.js +8 -5
- package/lib/commonjs/app-shell/AppShellBars.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellEngine.js +81 -36
- package/lib/commonjs/app-shell/AppShellEngine.js.map +1 -1
- package/lib/commonjs/app-shell/AppShellSplit.js +26 -14
- package/lib/commonjs/app-shell/AppShellSplit.js.map +1 -1
- package/lib/commonjs/appearance/BloomScope.js +8 -3
- package/lib/commonjs/appearance/BloomScope.js.map +1 -1
- package/lib/commonjs/appearance/context.js.map +1 -1
- package/lib/commonjs/chip/Chip.js +51 -7
- package/lib/commonjs/chip/Chip.js.map +1 -1
- package/lib/commonjs/content-panel/ContentPanel.js +40 -24
- package/lib/commonjs/content-panel/ContentPanel.js.map +1 -1
- package/lib/commonjs/content-panel/ContentPanel.web.js +45 -18
- package/lib/commonjs/content-panel/ContentPanel.web.js.map +1 -1
- package/lib/commonjs/design-tokens/shapes.js +2 -2
- package/lib/commonjs/design-tokens/shapes.js.map +1 -1
- package/lib/commonjs/design-tokens/tokens.json +2 -2
- package/lib/commonjs/error-boundary/ErrorBoundary.js +10 -0
- package/lib/commonjs/error-boundary/ErrorBoundary.js.map +1 -1
- package/lib/commonjs/error-boundary/PanelErrorBoundary.js +64 -0
- package/lib/commonjs/error-boundary/PanelErrorBoundary.js.map +1 -0
- package/lib/commonjs/error-boundary/index.js +7 -0
- package/lib/commonjs/error-boundary/index.js.map +1 -1
- package/lib/commonjs/index.js +946 -927
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +946 -927
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/mail-thread/MailMessage.js +8 -14
- package/lib/commonjs/mail-thread/MailMessage.js.map +1 -1
- package/lib/commonjs/mail-thread/MailThread.js +6 -2
- package/lib/commonjs/mail-thread/MailThread.js.map +1 -1
- package/lib/commonjs/mail-thread/shared.js +2 -1
- package/lib/commonjs/mail-thread/shared.js.map +1 -1
- package/lib/commonjs/page-footer/PageFooter.js +137 -0
- package/lib/commonjs/page-footer/PageFooter.js.map +1 -0
- package/lib/commonjs/page-footer/PageFooterProvider.js +22 -0
- package/lib/commonjs/page-footer/PageFooterProvider.js.map +1 -0
- package/lib/commonjs/page-footer/context.js +11 -0
- package/lib/commonjs/page-footer/context.js.map +1 -0
- package/lib/commonjs/page-footer/index.js +27 -0
- package/lib/commonjs/page-footer/index.js.map +1 -0
- package/lib/commonjs/page-footer/types.js +6 -0
- package/lib/commonjs/page-footer/types.js.map +1 -0
- package/lib/commonjs/page-footer/use-page-footer-inset.js +19 -0
- package/lib/commonjs/page-footer/use-page-footer-inset.js.map +1 -0
- package/lib/commonjs/settings-modal/SettingsPlanCard.js +11 -7
- package/lib/commonjs/settings-modal/SettingsPlanCard.js.map +1 -1
- package/lib/commonjs/sidebar/Sidebar.js +6 -5
- package/lib/commonjs/sidebar/Sidebar.js.map +1 -1
- package/lib/commonjs/styles/panel-chrome.js +15 -1
- package/lib/commonjs/styles/panel-chrome.js.map +1 -1
- package/lib/module/app-shell/AppShell.js +3 -2
- package/lib/module/app-shell/AppShell.js.map +1 -1
- package/lib/module/app-shell/AppShellBars.js +8 -5
- package/lib/module/app-shell/AppShellBars.js.map +1 -1
- package/lib/module/app-shell/AppShellEngine.js +81 -36
- package/lib/module/app-shell/AppShellEngine.js.map +1 -1
- package/lib/module/app-shell/AppShellSplit.js +26 -14
- package/lib/module/app-shell/AppShellSplit.js.map +1 -1
- package/lib/module/appearance/BloomScope.js +8 -3
- package/lib/module/appearance/BloomScope.js.map +1 -1
- package/lib/module/appearance/context.js.map +1 -1
- package/lib/module/chip/Chip.js +51 -7
- package/lib/module/chip/Chip.js.map +1 -1
- package/lib/module/content-panel/ContentPanel.js +41 -25
- package/lib/module/content-panel/ContentPanel.js.map +1 -1
- package/lib/module/content-panel/ContentPanel.web.js +47 -20
- package/lib/module/content-panel/ContentPanel.web.js.map +1 -1
- package/lib/module/design-tokens/shapes.js +2 -2
- package/lib/module/design-tokens/shapes.js.map +1 -1
- package/lib/module/design-tokens/tokens.json +2 -2
- package/lib/module/error-boundary/ErrorBoundary.js +10 -0
- package/lib/module/error-boundary/ErrorBoundary.js.map +1 -1
- package/lib/module/error-boundary/PanelErrorBoundary.js +59 -0
- package/lib/module/error-boundary/PanelErrorBoundary.js.map +1 -0
- package/lib/module/error-boundary/index.js +1 -0
- package/lib/module/error-boundary/index.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/mail-thread/MailMessage.js +8 -14
- package/lib/module/mail-thread/MailMessage.js.map +1 -1
- package/lib/module/mail-thread/MailThread.js +6 -2
- package/lib/module/mail-thread/MailThread.js.map +1 -1
- package/lib/module/mail-thread/shared.js +2 -1
- package/lib/module/mail-thread/shared.js.map +1 -1
- package/lib/module/page-footer/PageFooter.js +132 -0
- package/lib/module/page-footer/PageFooter.js.map +1 -0
- package/lib/module/page-footer/PageFooterProvider.js +18 -0
- package/lib/module/page-footer/PageFooterProvider.js.map +1 -0
- package/lib/module/page-footer/context.js +7 -0
- package/lib/module/page-footer/context.js.map +1 -0
- package/lib/module/page-footer/index.js +6 -0
- package/lib/module/page-footer/index.js.map +1 -0
- package/lib/module/page-footer/types.js +4 -0
- package/lib/module/page-footer/types.js.map +1 -0
- package/lib/module/page-footer/use-page-footer-inset.js +16 -0
- package/lib/module/page-footer/use-page-footer-inset.js.map +1 -0
- package/lib/module/settings-modal/SettingsPlanCard.js +7 -3
- package/lib/module/settings-modal/SettingsPlanCard.js.map +1 -1
- package/lib/module/sidebar/Sidebar.js +6 -5
- package/lib/module/sidebar/Sidebar.js.map +1 -1
- package/lib/module/styles/panel-chrome.js +14 -1
- package/lib/module/styles/panel-chrome.js.map +1 -1
- package/lib/typescript/commonjs/app-shell/AppShellBars.d.ts +4 -0
- package/lib/typescript/commonjs/app-shell/AppShellBars.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/AppShellEngine.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/AppShellSplit.d.ts.map +1 -1
- package/lib/typescript/commonjs/app-shell/types.d.ts +16 -3
- package/lib/typescript/commonjs/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/BloomScope.d.ts +1 -1
- package/lib/typescript/commonjs/appearance/BloomScope.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/context.d.ts +2 -2
- package/lib/typescript/commonjs/appearance/context.d.ts.map +1 -1
- package/lib/typescript/commonjs/appearance/types.d.ts +4 -1
- package/lib/typescript/commonjs/appearance/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.d.ts +3 -4
- package/lib/typescript/commonjs/content-panel/ContentPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.web.d.ts +1 -1
- package/lib/typescript/commonjs/content-panel/ContentPanel.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/content-panel/types.d.ts +5 -2
- package/lib/typescript/commonjs/content-panel/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/commonjs/error-boundary/ErrorBoundary.d.ts +1 -0
- package/lib/typescript/commonjs/error-boundary/ErrorBoundary.d.ts.map +1 -1
- package/lib/typescript/commonjs/error-boundary/PanelErrorBoundary.d.ts +4 -0
- package/lib/typescript/commonjs/error-boundary/PanelErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/commonjs/error-boundary/index.d.ts +2 -1
- package/lib/typescript/commonjs/error-boundary/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/error-boundary/types.d.ts +16 -0
- package/lib/typescript/commonjs/error-boundary/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +3 -2
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/MailMessage.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/MailThread.d.ts +1 -1
- package/lib/typescript/commonjs/mail-thread/MailThread.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/shared.d.ts +1 -1
- package/lib/typescript/commonjs/mail-thread/shared.d.ts.map +1 -1
- package/lib/typescript/commonjs/mail-thread/types.d.ts +2 -0
- package/lib/typescript/commonjs/mail-thread/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/page-footer/PageFooter.d.ts +7 -0
- package/lib/typescript/commonjs/page-footer/PageFooter.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/PageFooterProvider.d.ts +4 -0
- package/lib/typescript/commonjs/page-footer/PageFooterProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/context.d.ts +3 -0
- package/lib/typescript/commonjs/page-footer/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/index.d.ts +5 -0
- package/lib/typescript/commonjs/page-footer/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/types.d.ts +26 -0
- package/lib/typescript/commonjs/page-footer/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/page-footer/use-page-footer-inset.d.ts +7 -0
- package/lib/typescript/commonjs/page-footer/use-page-footer-inset.d.ts.map +1 -0
- package/lib/typescript/commonjs/settings-modal/SettingsPlanCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/settings-modal/types.d.ts +3 -0
- package/lib/typescript/commonjs/settings-modal/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/commonjs/sidebar/types.d.ts +1 -1
- package/lib/typescript/commonjs/styles/panel-chrome.d.ts +5 -0
- package/lib/typescript/commonjs/styles/panel-chrome.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/AppShellBars.d.ts +4 -0
- package/lib/typescript/module/app-shell/AppShellBars.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/AppShellEngine.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/AppShellSplit.d.ts.map +1 -1
- package/lib/typescript/module/app-shell/types.d.ts +16 -3
- package/lib/typescript/module/app-shell/types.d.ts.map +1 -1
- package/lib/typescript/module/appearance/BloomScope.d.ts +1 -1
- package/lib/typescript/module/appearance/BloomScope.d.ts.map +1 -1
- package/lib/typescript/module/appearance/context.d.ts +2 -2
- package/lib/typescript/module/appearance/context.d.ts.map +1 -1
- package/lib/typescript/module/appearance/types.d.ts +4 -1
- package/lib/typescript/module/appearance/types.d.ts.map +1 -1
- package/lib/typescript/module/chip/Chip.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.d.ts +3 -4
- package/lib/typescript/module/content-panel/ContentPanel.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.web.d.ts +1 -1
- package/lib/typescript/module/content-panel/ContentPanel.web.d.ts.map +1 -1
- package/lib/typescript/module/content-panel/types.d.ts +5 -2
- package/lib/typescript/module/content-panel/types.d.ts.map +1 -1
- package/lib/typescript/module/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/module/error-boundary/ErrorBoundary.d.ts +1 -0
- package/lib/typescript/module/error-boundary/ErrorBoundary.d.ts.map +1 -1
- package/lib/typescript/module/error-boundary/PanelErrorBoundary.d.ts +4 -0
- package/lib/typescript/module/error-boundary/PanelErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/module/error-boundary/index.d.ts +2 -1
- package/lib/typescript/module/error-boundary/index.d.ts.map +1 -1
- package/lib/typescript/module/error-boundary/types.d.ts +16 -0
- package/lib/typescript/module/error-boundary/types.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +3 -2
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/MailMessage.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/MailThread.d.ts +1 -1
- package/lib/typescript/module/mail-thread/MailThread.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/shared.d.ts +1 -1
- package/lib/typescript/module/mail-thread/shared.d.ts.map +1 -1
- package/lib/typescript/module/mail-thread/types.d.ts +2 -0
- package/lib/typescript/module/mail-thread/types.d.ts.map +1 -1
- package/lib/typescript/module/page-footer/PageFooter.d.ts +7 -0
- package/lib/typescript/module/page-footer/PageFooter.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/PageFooterProvider.d.ts +4 -0
- package/lib/typescript/module/page-footer/PageFooterProvider.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/context.d.ts +3 -0
- package/lib/typescript/module/page-footer/context.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/index.d.ts +5 -0
- package/lib/typescript/module/page-footer/index.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/types.d.ts +26 -0
- package/lib/typescript/module/page-footer/types.d.ts.map +1 -0
- package/lib/typescript/module/page-footer/use-page-footer-inset.d.ts +7 -0
- package/lib/typescript/module/page-footer/use-page-footer-inset.d.ts.map +1 -0
- package/lib/typescript/module/settings-modal/SettingsPlanCard.d.ts.map +1 -1
- package/lib/typescript/module/settings-modal/types.d.ts +3 -0
- package/lib/typescript/module/settings-modal/types.d.ts.map +1 -1
- package/lib/typescript/module/sidebar/Sidebar.d.ts.map +1 -1
- package/lib/typescript/module/sidebar/types.d.ts +1 -1
- package/lib/typescript/module/styles/panel-chrome.d.ts +5 -0
- package/lib/typescript/module/styles/panel-chrome.d.ts.map +1 -1
- package/package.json +18 -1
- package/src/__tests__/support/adoption-matrix.ts +683 -0
- package/src/__tests__/support/card-surface.ts +25 -0
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/commerce-harness.tsx +97 -0
- package/src/__tests__/support/composite.ts +68 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/messages-in.ts +10 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/__tests__/support/worklet-capture-fixture.tsx +28 -0
- package/src/agent-avatar/__tests__/artwork-golden.json +774 -0
- package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +26 -0
- package/src/app-shell/AppShell.tsx +2 -2
- package/src/app-shell/AppShellBars.tsx +6 -4
- package/src/app-shell/AppShellEngine.tsx +51 -18
- package/src/app-shell/AppShellSplit.tsx +16 -7
- package/src/app-shell/types.ts +16 -3
- package/src/appearance/BloomScope.tsx +5 -2
- package/src/appearance/context.ts +2 -2
- package/src/appearance/types.ts +4 -1
- package/src/chip/Chip.tsx +26 -5
- package/src/content-panel/ContentPanel.tsx +35 -26
- package/src/content-panel/ContentPanel.web.tsx +35 -24
- package/src/content-panel/types.ts +5 -2
- package/src/design-tokens/shapes.ts +1 -1
- package/src/design-tokens/tokens.json +2 -2
- package/src/error-boundary/ErrorBoundary.tsx +6 -0
- package/src/error-boundary/PanelErrorBoundary.tsx +38 -0
- package/src/error-boundary/index.ts +3 -0
- package/src/error-boundary/types.ts +18 -0
- package/src/index.ts +4 -1
- package/src/index.web.ts +4 -1
- package/src/mail-thread/MailMessage.tsx +9 -11
- package/src/mail-thread/MailThread.tsx +3 -1
- package/src/mail-thread/shared.ts +2 -1
- package/src/mail-thread/types.ts +2 -0
- package/src/page-footer/PageFooter.tsx +96 -0
- package/src/page-footer/PageFooterProvider.tsx +9 -0
- package/src/page-footer/context.ts +6 -0
- package/src/page-footer/index.ts +4 -0
- package/src/page-footer/types.ts +26 -0
- package/src/page-footer/use-page-footer-inset.ts +17 -0
- package/src/settings-modal/SettingsPlanCard.tsx +2 -0
- package/src/settings-modal/types.ts +3 -0
- package/src/sidebar/Sidebar.tsx +5 -4
- package/src/sidebar/types.ts +1 -1
- package/src/styles/panel-chrome.ts +9 -1
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +9346 -0
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +69 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PanelErrorBoundary } from '../error-boundary/PanelErrorBoundary';
|
|
1
2
|
import { resolveSurfaceMaterial } from '../surface/resolve-surface-material';
|
|
2
3
|
/**
|
|
3
4
|
* Native variant of `ContentPanel` — the framed app-content surface.
|
|
@@ -11,13 +12,12 @@ import { resolveSurfaceMaterial } from '../surface/resolve-surface-material';
|
|
|
11
12
|
* On NATIVE none of the WEB overlay machinery applies: there is no document
|
|
12
13
|
* scroll, no sticky positioning, and no bleed to mask, so the panel is simply a
|
|
13
14
|
* surface wrapping its content. The `framed` prop still drives whether that
|
|
14
|
-
* surface is rounded + bordered, tri-state
|
|
15
|
-
* (the
|
|
16
|
-
* native too — the material follows the same window breakpoint):
|
|
15
|
+
* surface is rounded + bordered, tri-state with responsive NativeWind borders
|
|
16
|
+
* (the material and scoped radius follow the same window breakpoint):
|
|
17
17
|
*
|
|
18
18
|
* - `undefined` (DEFAULT) → responsive: full-bleed below the `framedFrom`
|
|
19
19
|
* breakpoint (default `768` / Tailwind `md`), rounded + bordered at/above it
|
|
20
|
-
* (e.g. `md:
|
|
20
|
+
* (e.g. `md:border md:border-border`).
|
|
21
21
|
* - `false` → never framed (plain full-bleed at every size).
|
|
22
22
|
* - `true` → always rounded + bordered.
|
|
23
23
|
*
|
|
@@ -35,7 +35,8 @@ import React, { memo } from 'react';
|
|
|
35
35
|
import { SurfacePaint } from '../surface/SurfacePaint';
|
|
36
36
|
import { StyleSheet, useWindowDimensions } from 'react-native';
|
|
37
37
|
|
|
38
|
-
import { useOptionalPanelChrome } from '../styles/panel-chrome';
|
|
38
|
+
import { useOptionalPanelChrome, usePanelShape } from '../styles/panel-chrome';
|
|
39
|
+
import { surfaceStyle as resolveShapeStyle } from '../shapes/surface-style';
|
|
39
40
|
import { StyledView } from '../styles/styled-primitives';
|
|
40
41
|
import { SurfaceLevelProvider, surfaceFillVars, useOptionalSurfaceFill } from '../styles/surface-levels';
|
|
41
42
|
import {
|
|
@@ -63,21 +64,22 @@ export const PANEL_BOTTOM_INSET = 8;
|
|
|
63
64
|
* value is the only runtime decision.
|
|
64
65
|
*/
|
|
65
66
|
const RESPONSIVE_SURFACE: Record<ContentPanelFramedBreakpoint, string> = {
|
|
66
|
-
500: 'min-[500px]:
|
|
67
|
-
640: 'sm:
|
|
68
|
-
768: 'md:
|
|
69
|
-
1024: 'lg:
|
|
67
|
+
500: 'min-[500px]:border min-[500px]:border-border',
|
|
68
|
+
640: 'sm:border sm:border-border',
|
|
69
|
+
768: 'md:border md:border-border',
|
|
70
|
+
1024: 'lg:border lg:border-border',
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
const RESPONSIVE_CONTENT: Record<ContentPanelFramedBreakpoint, string> = {
|
|
73
|
-
500: 'min-[500px]:overflow-hidden
|
|
74
|
-
640: 'sm:overflow-hidden
|
|
75
|
-
768: 'md:overflow-hidden
|
|
76
|
-
1024: 'lg:overflow-hidden
|
|
74
|
+
500: 'min-[500px]:overflow-hidden',
|
|
75
|
+
640: 'sm:overflow-hidden',
|
|
76
|
+
768: 'md:overflow-hidden',
|
|
77
|
+
1024: 'lg:overflow-hidden',
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
80
81
|
children,
|
|
82
|
+
errorBoundary = false,
|
|
81
83
|
framed,
|
|
82
84
|
framedFrom = 768,
|
|
83
85
|
fill = false,
|
|
@@ -91,33 +93,38 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
91
93
|
}) => {
|
|
92
94
|
// Dev-only invariant — a ContentPanel must never be nested inside another.
|
|
93
95
|
useContentPanelNestingGuard();
|
|
96
|
+
const panelShape = usePanelShape();
|
|
94
97
|
const panelChrome = useOptionalPanelChrome();
|
|
95
98
|
// What the panel tells its subtree it is painted in (`./shared.ts`).
|
|
96
99
|
const rawFill = usePanelSurfaceFill(surfaceClassName, surfaceStyle, surfaceColor);
|
|
97
100
|
const defaultFill = rawFill;
|
|
98
101
|
const { width } = useWindowDimensions();
|
|
99
102
|
const parentFill = useOptionalSurfaceFill();
|
|
100
|
-
const paintsSurface = Boolean(defaultFill) && parentFill !== undefined && (framed ?? width >= framedFrom);
|
|
103
|
+
const paintsSurface = Boolean(defaultFill) && parentFill !== undefined && (framed ?? width >= framedFrom) && chrome !== 'none';
|
|
101
104
|
const publishedFill = rawFill && paintsSurface ? resolveSurfaceMaterial({ fill: rawFill, parentFill: parentFill! }).publishedFill : rawFill;
|
|
102
|
-
const radius = StyleSheet.flatten(surfaceStyle)?.borderRadius ??
|
|
105
|
+
const radius = StyleSheet.flatten(surfaceStyle)?.borderRadius ?? panelShape.radius;
|
|
106
|
+
const isFramed = framed ?? width >= framedFrom;
|
|
107
|
+
const geometry = { ...resolveShapeStyle({ curve: panelShape.curve }), borderRadius: isFramed ? radius : 0 };
|
|
103
108
|
|
|
104
109
|
// Tri-state: `undefined` → responsive (breakpoint-gated), `true` → always
|
|
105
110
|
// framed, `false` → never framed (plain full-bleed). Whole literal class
|
|
106
111
|
// strings are selected per mode so the Tailwind content-scan over `src/**`
|
|
107
|
-
// picks up every `min-*`/`sm:`/`md:`/`lg:`
|
|
112
|
+
// picks up every `min-*`/`sm:`/`md:`/`lg:` token verbatim
|
|
108
113
|
// (the breakpoint bundle lives whole in `RESPONSIVE_SURFACE`; `framed ===
|
|
109
114
|
// true`/`false` ignore `framedFrom` for fixed always/never framing).
|
|
110
115
|
const surfaceBase =
|
|
111
|
-
|
|
112
|
-
?
|
|
113
|
-
: framed
|
|
114
|
-
?
|
|
115
|
-
:
|
|
116
|
+
chrome === 'none'
|
|
117
|
+
? 'flex-1'
|
|
118
|
+
: framed === undefined
|
|
119
|
+
? `flex-1 ${RESPONSIVE_SURFACE[framedFrom]}`
|
|
120
|
+
: framed
|
|
121
|
+
? 'flex-1 border border-border'
|
|
122
|
+
: 'flex-1';
|
|
116
123
|
const surfaceClass = [surfaceBase, fill ? 'min-h-0' : '', surfaceClassName ?? (paintsSurface ? 'bg-transparent' : 'bg-card')].filter(Boolean).join(' ');
|
|
117
124
|
// Native has no overlays: the surface itself carries the edge. `none` drops
|
|
118
125
|
// both, `border` keeps the class-drawn hairline, `elevated` adds the lift.
|
|
119
126
|
const chromeStyle =
|
|
120
|
-
|
|
127
|
+
!isFramed || chrome === 'none' || chrome === 'border'
|
|
121
128
|
? null
|
|
122
129
|
: shadow || panelChrome
|
|
123
130
|
? { boxShadow: shadow ?? panelChrome?.shadow }
|
|
@@ -127,28 +134,30 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
127
134
|
// bottom and a list inside it scrolls within it. What the prop adds here is
|
|
128
135
|
// `min-h-0`, so a tall child cannot push the panel past the box it was given
|
|
129
136
|
// — the same automatic-minimum-size trap as on web.
|
|
130
|
-
const contentClass = ['flex-1', framed === true ? 'overflow-hidden
|
|
137
|
+
const contentClass = ['flex-1', framed === true ? 'overflow-hidden' : framed === undefined ? RESPONSIVE_CONTENT[framedFrom] : '', fill ? 'min-h-0' : '', contentClassName].filter(Boolean).join(' ');
|
|
131
138
|
|
|
132
139
|
return (
|
|
133
140
|
<ContentPanelNestingContext.Provider value={true}>
|
|
134
141
|
<StyledView
|
|
142
|
+
testID="content-panel-surface"
|
|
135
143
|
className={surfaceClass}
|
|
136
144
|
style={[
|
|
137
145
|
// The surface variable rides the element that carries the fill, so
|
|
138
146
|
// the two can never disagree. A no-op on native; the provider below
|
|
139
147
|
// is what answers there. (`styles/surface-levels.ts`.)
|
|
140
148
|
surfaceFillVars(publishedFill),
|
|
149
|
+
geometry,
|
|
141
150
|
chromeStyle,
|
|
142
151
|
surfaceStyle,
|
|
143
152
|
paintsSurface ? { backgroundColor: 'transparent' } : null,
|
|
144
153
|
]}
|
|
145
154
|
>
|
|
146
|
-
{paintsSurface ? <SurfacePaint fill={defaultFill!} radius={radius} /> : null}
|
|
147
|
-
<StyledView className={contentClass} style={[
|
|
155
|
+
{paintsSurface ? <SurfacePaint fill={defaultFill!} radius={radius} shape={{ curve: panelShape.curve }} /> : null}
|
|
156
|
+
<StyledView testID="content-panel-content" className={contentClass} style={[geometry, contentStyle]}>
|
|
148
157
|
{/* The panel is a surface: everything inside is sitting on rung 1,
|
|
149
158
|
painted in the colour this panel actually paints. */}
|
|
150
159
|
<SurfaceLevelProvider level={1} fill={publishedFill}>
|
|
151
|
-
{children}
|
|
160
|
+
{errorBoundary === false ? children : <PanelErrorBoundary {...(errorBoundary === true ? {} : errorBoundary)}>{children}</PanelErrorBoundary>}
|
|
152
161
|
</SurfaceLevelProvider>
|
|
153
162
|
</StyledView>
|
|
154
163
|
</StyledView>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PanelErrorBoundary } from '../error-boundary/PanelErrorBoundary';
|
|
1
2
|
import { resolveSurfaceMaterial } from '../surface/resolve-surface-material';
|
|
2
3
|
/**
|
|
3
4
|
* Web variant of `ContentPanel` — the framed app-content surface.
|
|
@@ -83,18 +84,19 @@ import { resolveSurfaceMaterial } from '../surface/resolve-surface-material';
|
|
|
83
84
|
*
|
|
84
85
|
* Styling is NativeWind-className-first; the literal class strings below MUST
|
|
85
86
|
* stay literal so a consumer's Tailwind content-scan over `lib/**` picks them up
|
|
86
|
-
* (no dynamic concatenation of the arbitrary `web:[…]` / `rounded-radius
|
|
87
|
+
* (no dynamic concatenation of the arbitrary `web:[…]` / `rounded-[var(--bloom-panel-radius)]` /
|
|
87
88
|
* `md:` parts — whole class strings are selected per mode instead).
|
|
88
89
|
*/
|
|
89
90
|
import React, { memo, useInsertionEffect } from 'react';
|
|
90
91
|
import { useSurfaceMaterial } from '../surface/use-surface-material.web';
|
|
91
|
-
import { useWindowDimensions, type ViewStyle } from 'react-native';
|
|
92
|
+
import { StyleSheet, useWindowDimensions, type ViewStyle } from 'react-native';
|
|
92
93
|
|
|
93
94
|
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
94
95
|
import type { WebCssStyle } from '../styles/web-view-style';
|
|
96
|
+
import { surfaceStyle as resolveShapeStyle } from '../shapes/surface-style';
|
|
95
97
|
import { StyledView } from '../styles/styled-primitives';
|
|
96
98
|
|
|
97
|
-
import { useOptionalPanelChrome } from '../styles/panel-chrome';
|
|
99
|
+
import { useOptionalPanelChrome, usePanelShape } from '../styles/panel-chrome';
|
|
98
100
|
import { SurfaceLevelProvider, surfaceFillVars, useOptionalSurfaceFill } from '../styles/surface-levels';
|
|
99
101
|
import { useTheme } from '../theme/use-theme';
|
|
100
102
|
import {
|
|
@@ -130,23 +132,23 @@ const RESPONSIVE_WEB: Record<
|
|
|
130
132
|
{ surface: string; content: string; overlayHidden: string }
|
|
131
133
|
> = {
|
|
132
134
|
500: {
|
|
133
|
-
surface: 'min-[500px]:rounded-radius
|
|
134
|
-
content: 'min-[500px]:rounded-radius
|
|
135
|
+
surface: 'min-[500px]:rounded-[var(--bloom-panel-radius)]',
|
|
136
|
+
content: 'min-[500px]:rounded-[var(--bloom-panel-radius)] web:min-[500px]:overflow-x-clip',
|
|
135
137
|
overlayHidden: 'max-[500px]:hidden',
|
|
136
138
|
},
|
|
137
139
|
640: {
|
|
138
|
-
surface: 'sm:rounded-radius
|
|
139
|
-
content: 'sm:rounded-radius
|
|
140
|
+
surface: 'sm:rounded-[var(--bloom-panel-radius)]',
|
|
141
|
+
content: 'sm:rounded-[var(--bloom-panel-radius)] web:sm:overflow-x-clip',
|
|
140
142
|
overlayHidden: 'max-sm:hidden',
|
|
141
143
|
},
|
|
142
144
|
768: {
|
|
143
|
-
surface: 'md:rounded-radius
|
|
144
|
-
content: 'md:rounded-radius
|
|
145
|
+
surface: 'md:rounded-[var(--bloom-panel-radius)]',
|
|
146
|
+
content: 'md:rounded-[var(--bloom-panel-radius)] web:md:overflow-x-clip',
|
|
145
147
|
overlayHidden: 'max-md:hidden',
|
|
146
148
|
},
|
|
147
149
|
1024: {
|
|
148
|
-
surface: 'lg:rounded-radius
|
|
149
|
-
content: 'lg:rounded-radius
|
|
150
|
+
surface: 'lg:rounded-[var(--bloom-panel-radius)]',
|
|
151
|
+
content: 'lg:rounded-[var(--bloom-panel-radius)] web:lg:overflow-x-clip',
|
|
150
152
|
overlayHidden: 'max-lg:hidden',
|
|
151
153
|
},
|
|
152
154
|
};
|
|
@@ -170,6 +172,7 @@ const PANEL_INSET_CSS = `
|
|
|
170
172
|
|
|
171
173
|
const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
172
174
|
children,
|
|
175
|
+
errorBoundary = false,
|
|
173
176
|
framed,
|
|
174
177
|
framedFrom = 768,
|
|
175
178
|
fill = false,
|
|
@@ -197,13 +200,18 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
197
200
|
// The floating-panel edge the `Sidebar` wears. Optional: the panel drew its
|
|
198
201
|
// own surface long before it had a shadow, so it must not start throwing
|
|
199
202
|
// outside a provider.
|
|
203
|
+
const panelShape = usePanelShape();
|
|
204
|
+
const radius = StyleSheet.flatten(surfaceStyle)?.borderRadius ?? panelShape.radius;
|
|
205
|
+
const curveStyle = resolveShapeStyle({ curve: panelShape.curve });
|
|
206
|
+
const panelStyle = { ...curveStyle, borderRadius: radius };
|
|
207
|
+
const panelVars: WebCssStyle = { '--bloom-panel-radius': typeof radius === 'string' ? radius : `${typeof radius === 'number' ? radius : panelShape.radius}px` };
|
|
200
208
|
const panelChrome = useOptionalPanelChrome();
|
|
201
209
|
// What the panel tells its subtree it is painted in (`./shared.ts`).
|
|
202
210
|
const rawFill = usePanelSurfaceFill(surfaceClassName, surfaceStyle, surfaceColor);
|
|
203
211
|
const defaultFill = rawFill;
|
|
204
212
|
const { width } = useWindowDimensions();
|
|
205
213
|
const parentFill = useOptionalSurfaceFill();
|
|
206
|
-
const paintsSurface = Boolean(defaultFill) && parentFill !== undefined && (framed ?? width >= framedFrom);
|
|
214
|
+
const paintsSurface = Boolean(defaultFill) && parentFill !== undefined && (framed ?? width >= framedFrom) && chrome !== 'none';
|
|
207
215
|
const publishedFill = rawFill && paintsSurface ? resolveSurfaceMaterial({ fill: rawFill, parentFill: parentFill! }).publishedFill : rawFill;
|
|
208
216
|
const materialStyle = useSurfaceMaterial('[data-bloom-panel-material]', 'bloom-content-panel-material', defaultFill ?? 'transparent');
|
|
209
217
|
|
|
@@ -223,12 +231,12 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
223
231
|
const surfaceBase = responsive
|
|
224
232
|
? `flex-1 ${bp.surface}`
|
|
225
233
|
: framed
|
|
226
|
-
? 'flex-1 rounded-radius
|
|
234
|
+
? 'flex-1 rounded-[var(--bloom-panel-radius)]'
|
|
227
235
|
: 'flex-1';
|
|
228
236
|
const contentBase = responsive
|
|
229
237
|
? `flex-1 ${bp.content}`
|
|
230
238
|
: framed
|
|
231
|
-
? 'flex-1 rounded-radius
|
|
239
|
+
? 'flex-1 rounded-[var(--bloom-panel-radius)] web:overflow-x-clip'
|
|
232
240
|
: 'flex-1';
|
|
233
241
|
// `fill`: the panel takes the height its parent gives it and the CONTENT
|
|
234
242
|
// scrolls inside. `min-h-0` on both boxes is the whole trick — a flex child's
|
|
@@ -300,6 +308,9 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
300
308
|
// disagree about what the panel painted.
|
|
301
309
|
surfaceFillVars(publishedFill),
|
|
302
310
|
insetVars,
|
|
311
|
+
panelVars,
|
|
312
|
+
curveStyle,
|
|
313
|
+
framed === true ? { borderRadius: radius } : framed === false ? { borderRadius: 0 } : null,
|
|
303
314
|
surfaceStyle,
|
|
304
315
|
paintsSurface ? materialStyle : null,
|
|
305
316
|
]}
|
|
@@ -315,12 +326,12 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
315
326
|
pointerEvents="none"
|
|
316
327
|
className={
|
|
317
328
|
boundToPanel
|
|
318
|
-
? `web:[grid-area:1/1] z-30 h-full w-full rounded-radius
|
|
329
|
+
? `web:[grid-area:1/1] z-30 h-full w-full rounded-[var(--bloom-panel-radius)] ${responsive ? bp.overlayHidden : ''} web:[clip-path:inset(0)]`
|
|
319
330
|
: responsive
|
|
320
|
-
? `web:sticky web:top-2 z-30 h-[calc(100dvh-16px)] w-full rounded-radius
|
|
321
|
-
: 'web:sticky web:top-2 z-30 h-[calc(100dvh-16px)] w-full rounded-radius
|
|
331
|
+
? `web:sticky web:top-2 z-30 h-[calc(100dvh-16px)] w-full rounded-[var(--bloom-panel-radius)] ${bp.overlayHidden} web:[margin-bottom:calc(-100dvh+16px)]`
|
|
332
|
+
: 'web:sticky web:top-2 z-30 h-[calc(100dvh-16px)] w-full rounded-[var(--bloom-panel-radius)] web:[margin-bottom:calc(-100dvh+16px)]'
|
|
322
333
|
}
|
|
323
|
-
style={{ ...insetStyle, boxShadow: `0 0 0 ${maskSpread}px ${maskColor ?? colors.background}` }}
|
|
334
|
+
style={{ ...panelStyle, ...insetStyle, boxShadow: `0 0 0 ${maskSpread}px ${maskColor ?? colors.background}` }}
|
|
324
335
|
/>
|
|
325
336
|
)}
|
|
326
337
|
{/* (2) Border-frame overlay — one continuous rounded border, above all.
|
|
@@ -333,15 +344,15 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
333
344
|
// The shadow rides the SAME element as the hairline, so the lift and
|
|
334
345
|
// the edge can never disagree about where the panel ends. It paints
|
|
335
346
|
// outward, into the gutter the page background shows.
|
|
336
|
-
style={[insetStyle, chrome === 'elevated' && (shadow || panelChrome)
|
|
347
|
+
style={[panelStyle, insetStyle, chrome === 'elevated' && (shadow || panelChrome)
|
|
337
348
|
? { boxShadow: shadow ?? panelChrome?.shadow }
|
|
338
349
|
: null]}
|
|
339
350
|
className={
|
|
340
351
|
boundToPanel
|
|
341
|
-
? `web:[grid-area:1/1] z-[120] h-full w-full rounded-radius
|
|
352
|
+
? `web:[grid-area:1/1] z-[120] h-full w-full rounded-[var(--bloom-panel-radius)] border border-border ${responsive ? bp.overlayHidden : ''}`
|
|
342
353
|
: responsive
|
|
343
|
-
? `web:sticky web:top-2 z-[120] h-[calc(100dvh-16px)] w-full rounded-radius
|
|
344
|
-
: 'web:sticky web:top-2 z-[120] h-[calc(100dvh-16px)] w-full rounded-radius
|
|
354
|
+
? `web:sticky web:top-2 z-[120] h-[calc(100dvh-16px)] w-full rounded-[var(--bloom-panel-radius)] border border-border ${bp.overlayHidden} web:[margin-bottom:calc(-100dvh+16px)]`
|
|
355
|
+
: 'web:sticky web:top-2 z-[120] h-[calc(100dvh-16px)] w-full rounded-[var(--bloom-panel-radius)] border border-border web:[margin-bottom:calc(-100dvh+16px)]'
|
|
345
356
|
}
|
|
346
357
|
/>
|
|
347
358
|
)}
|
|
@@ -349,11 +360,11 @@ const ContentPanelComponent: React.FC<ContentPanelProps> = ({
|
|
|
349
360
|
in place instead of remounting `{children}` (which would reset feed
|
|
350
361
|
scroll/virtualizer + refetch on a breakpoint cross). Clipped to the
|
|
351
362
|
rounded panel shape on web when framed. */}
|
|
352
|
-
<StyledView key="content" testID="content-panel-content" className={contentClass} style={contentStyle}>
|
|
363
|
+
<StyledView key="content" testID="content-panel-content" className={contentClass} style={[curveStyle, framed === true ? { borderRadius: radius } : framed === false ? { borderRadius: 0 } : null, contentStyle]}>
|
|
353
364
|
{/* The panel is a surface: everything inside is sitting on rung 1,
|
|
354
365
|
painted in the colour this panel actually paints. */}
|
|
355
366
|
<SurfaceLevelProvider level={1} fill={publishedFill}>
|
|
356
|
-
{children}
|
|
367
|
+
{errorBoundary === false ? children : <PanelErrorBoundary {...(errorBoundary === true ? {} : errorBoundary)}>{children}</PanelErrorBoundary>}
|
|
357
368
|
</SurfaceLevelProvider>
|
|
358
369
|
</StyledView>
|
|
359
370
|
</StyledView>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { PanelErrorBoundaryOptions } from '../error-boundary/types';
|
|
2
3
|
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -17,8 +18,10 @@ export type ContentPanelChrome = 'elevated' | 'border' | 'none';
|
|
|
17
18
|
|
|
18
19
|
export interface ContentPanelProps {
|
|
19
20
|
children: React.ReactNode;
|
|
21
|
+
/** Opt in to local themed error recovery. Split panes already provide their own boundaries. */
|
|
22
|
+
errorBoundary?: boolean | PanelErrorBoundaryOptions;
|
|
20
23
|
/**
|
|
21
|
-
* Framing mode. Tri-state,
|
|
24
|
+
* Framing mode. Tri-state, managed by Bloom — no consumer
|
|
22
25
|
* breakpoint hook needed:
|
|
23
26
|
* - `undefined` (DEFAULT) → responsive: full-bleed below the `framedFrom`
|
|
24
27
|
* breakpoint, rounded + bordered at/above it.
|
|
@@ -41,7 +44,7 @@ export interface ContentPanelProps {
|
|
|
41
44
|
* surface lifted off the page rather than a box drawn on it.
|
|
42
45
|
* - `'border'` — the hairline alone, which is what this panel drew before.
|
|
43
46
|
* - `'none'` — no edge at all: a flat surface that still clips and rounds,
|
|
44
|
-
* for an app whose page background
|
|
47
|
+
* without material rim or sheen, for an app whose page background separates the two.
|
|
45
48
|
*
|
|
46
49
|
* Ignored while the panel is full-bleed (below `framedFrom`, or
|
|
47
50
|
* `framed={false}`) — there is no edge to draw.
|
|
@@ -4,7 +4,7 @@ import { RADIUS } from './scales';
|
|
|
4
4
|
/** Component policies. Geometry and renderers never import these tokens. */
|
|
5
5
|
export const SURFACE_SHAPES = {
|
|
6
6
|
card: { radius: RADIUS['radius-12'], curve: 'smooth' },
|
|
7
|
-
sidebar: { radius: RADIUS['radius-
|
|
7
|
+
sidebar: { radius: RADIUS['radius-28'], curve: 'round' },
|
|
8
8
|
panel: { radius: RADIUS['radius-20'], curve: 'smooth' },
|
|
9
9
|
menu: { radius: RADIUS['radius-16'], curve: 'smooth' },
|
|
10
10
|
sheet: { radius: { topStart: RADIUS['radius-24'], topEnd: RADIUS['radius-24'] }, curve: 'smooth' },
|
|
@@ -28596,12 +28596,12 @@
|
|
|
28596
28596
|
"sidebar": {
|
|
28597
28597
|
"$extensions": {
|
|
28598
28598
|
"so.oxy.bloom": {
|
|
28599
|
-
"curve": "
|
|
28599
|
+
"curve": "round"
|
|
28600
28600
|
}
|
|
28601
28601
|
},
|
|
28602
28602
|
"radius": {
|
|
28603
28603
|
"$type": "dimension",
|
|
28604
|
-
"$value": "
|
|
28604
|
+
"$value": "28px"
|
|
28605
28605
|
}
|
|
28606
28606
|
},
|
|
28607
28607
|
"panel": {
|
|
@@ -83,6 +83,12 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
83
83
|
this.props.onError?.(error, errorInfo);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
componentDidUpdate(previous: ErrorBoundaryProps): void {
|
|
87
|
+
if (this.state.hasError && !Object.is(previous.resetKey, this.props.resetKey)) {
|
|
88
|
+
this.setState({ hasError: false, error: null, errorInfo: null, retryCount: 0 });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
private handleRetry = (): void => {
|
|
87
93
|
this.setState((prev) => ({
|
|
88
94
|
hasError: false,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView } from 'react-native';
|
|
3
|
+
import { EmptyState } from '../empty-state';
|
|
4
|
+
import { useBottomEdgeInset } from '../layout/bottom-edge';
|
|
5
|
+
import { useMessages } from '../locale/messages';
|
|
6
|
+
import { space } from '../styles/tokens';
|
|
7
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
8
|
+
import { ERROR_BOUNDARY_MESSAGES } from './messages';
|
|
9
|
+
import type { PanelErrorBoundaryProps } from './types';
|
|
10
|
+
|
|
11
|
+
/** Local recovery inside a healthy Bloom shell. Catastrophic/provider errors belong to ErrorBoundary. */
|
|
12
|
+
export function PanelErrorBoundary({ children, fallback, emptyState, bottomInset, ...props }: PanelErrorBoundaryProps) {
|
|
13
|
+
const { messages } = useMessages(ERROR_BOUNDARY_MESSAGES);
|
|
14
|
+
const inheritedBottom = useBottomEdgeInset();
|
|
15
|
+
return (
|
|
16
|
+
<ErrorBoundary {...props} fallback={fallback !== undefined ? fallback : (({ retry }) => (
|
|
17
|
+
<ScrollView
|
|
18
|
+
style={{ flex: 1, minHeight: 0 }}
|
|
19
|
+
contentContainerStyle={{ flexGrow: 1, paddingHorizontal: space.md, paddingBottom: bottomInset ?? inheritedBottom }}
|
|
20
|
+
>
|
|
21
|
+
<EmptyState
|
|
22
|
+
variant="compact"
|
|
23
|
+
{...emptyState}
|
|
24
|
+
title={emptyState?.title ?? messages.title}
|
|
25
|
+
description={emptyState?.description ?? messages.message}
|
|
26
|
+
style={[{ flexGrow: 1 }, emptyState?.style]}
|
|
27
|
+
action={{
|
|
28
|
+
...emptyState?.action,
|
|
29
|
+
label: emptyState?.action?.label ?? messages.retry,
|
|
30
|
+
onPress: () => { emptyState?.action?.onPress?.(); retry(); },
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
</ScrollView>
|
|
34
|
+
))}>
|
|
35
|
+
{children}
|
|
36
|
+
</ErrorBoundary>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { ErrorBoundary } from './ErrorBoundary';
|
|
2
|
+
export { PanelErrorBoundary } from './PanelErrorBoundary';
|
|
2
3
|
export type {
|
|
3
4
|
ErrorBoundaryProps,
|
|
4
5
|
ErrorBoundaryFallback,
|
|
5
6
|
ErrorBoundaryFallbackContext,
|
|
7
|
+
PanelErrorBoundaryOptions,
|
|
8
|
+
PanelErrorBoundaryProps,
|
|
6
9
|
} from './types';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode, ErrorInfo } from 'react';
|
|
2
|
+
import type { EmptyStateProps } from '../empty-state/types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Context passed to a render-prop `fallback` when an ErrorBoundary catches an
|
|
@@ -38,6 +39,8 @@ export type ErrorBoundaryFallback =
|
|
|
38
39
|
|
|
39
40
|
export interface ErrorBoundaryProps {
|
|
40
41
|
children: ReactNode;
|
|
42
|
+
/** Changing this identity resets a failed boundary without remounting healthy children. */
|
|
43
|
+
resetKey?: string | number;
|
|
41
44
|
/**
|
|
42
45
|
* Custom fallback UI to render on error. Accepts either:
|
|
43
46
|
* - a ReactNode (static — same UI on every error), or
|
|
@@ -57,3 +60,18 @@ export interface ErrorBoundaryProps {
|
|
|
57
60
|
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
58
61
|
testID?: string;
|
|
59
62
|
}
|
|
63
|
+
|
|
64
|
+
/** Recovery UI for a panel inside a healthy Bloom provider. */
|
|
65
|
+
export interface PanelErrorBoundaryOptions {
|
|
66
|
+
resetKey?: ErrorBoundaryProps['resetKey'];
|
|
67
|
+
onError?: ErrorBoundaryProps['onError'];
|
|
68
|
+
fallback?: ErrorBoundaryFallback;
|
|
69
|
+
/** Defaults to localized error wording and a retry action. A supplied action runs before retry. */
|
|
70
|
+
emptyState?: EmptyStateProps;
|
|
71
|
+
/** Override inherited bottom chrome clearance when the parent has already reserved it. */
|
|
72
|
+
bottomInset?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PanelErrorBoundaryProps extends PanelErrorBoundaryOptions {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -204,6 +204,7 @@ export * from './patient-info-card';
|
|
|
204
204
|
export * from './sidebar';
|
|
205
205
|
export * from './app-shell';
|
|
206
206
|
export * from './page-header';
|
|
207
|
+
export * from './page-footer';
|
|
207
208
|
export * from './notification-center';
|
|
208
209
|
export * from './theme-toggle';
|
|
209
210
|
export * from './settings-modal';
|
|
@@ -219,11 +220,13 @@ export * from './task-list';
|
|
|
219
220
|
export * from './agent-chat';
|
|
220
221
|
export * from './ai-chat';
|
|
221
222
|
export * from './radio-indicator';
|
|
222
|
-
export { ErrorBoundary } from './error-boundary';
|
|
223
|
+
export { ErrorBoundary, PanelErrorBoundary } from './error-boundary';
|
|
223
224
|
export type {
|
|
224
225
|
ErrorBoundaryProps,
|
|
225
226
|
ErrorBoundaryFallback,
|
|
226
227
|
ErrorBoundaryFallbackContext,
|
|
228
|
+
PanelErrorBoundaryOptions,
|
|
229
|
+
PanelErrorBoundaryProps,
|
|
227
230
|
} from './error-boundary';
|
|
228
231
|
export * as Shapes from './shapes';
|
|
229
232
|
export * from './avatar';
|
package/src/index.web.ts
CHANGED
|
@@ -209,6 +209,7 @@ export * from './patient-info-card';
|
|
|
209
209
|
export * from './sidebar';
|
|
210
210
|
export * from './app-shell/index.web';
|
|
211
211
|
export * from './page-header';
|
|
212
|
+
export * from './page-footer';
|
|
212
213
|
export * from './notification-center';
|
|
213
214
|
export * from './theme-toggle';
|
|
214
215
|
export * from './settings-modal';
|
|
@@ -224,11 +225,13 @@ export * from './task-list';
|
|
|
224
225
|
export * from './agent-chat/index.web';
|
|
225
226
|
export * from './ai-chat/index.web';
|
|
226
227
|
export * from './radio-indicator';
|
|
227
|
-
export { ErrorBoundary } from './error-boundary';
|
|
228
|
+
export { ErrorBoundary, PanelErrorBoundary } from './error-boundary';
|
|
228
229
|
export type {
|
|
229
230
|
ErrorBoundaryProps,
|
|
230
231
|
ErrorBoundaryFallback,
|
|
231
232
|
ErrorBoundaryFallbackContext,
|
|
233
|
+
PanelErrorBoundaryOptions,
|
|
234
|
+
PanelErrorBoundaryProps,
|
|
232
235
|
} from './error-boundary';
|
|
233
236
|
export * as Shapes from './shapes';
|
|
234
237
|
export * from './avatar';
|
|
@@ -131,22 +131,20 @@ export function MailMessage({
|
|
|
131
131
|
: { key: 'forward', label: text.forward, icon: RiShareForwardLine, onPress: onForward },
|
|
132
132
|
].filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
|
133
133
|
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// COLLAPSED one paints nothing, so a thread reads as one list with a card
|
|
137
|
-
// opened inside it rather than as a column of blocks.
|
|
134
|
+
// Opening changes the content, not its surface or host. Keeping the Card
|
|
135
|
+
// mounted also preserves keyboard focus on the disclosure while it expands.
|
|
138
136
|
// Three labelled actions need about 320 of the message's own width; under
|
|
139
137
|
// that the group keeps the glyphs and drops the words, which is what
|
|
140
138
|
// `ButtonGroupItem` is built for — the accessible name is the label either
|
|
141
139
|
// way, so nothing is lost by the labels going.
|
|
142
140
|
const compactReplies = !replyLabelsFit(width);
|
|
143
|
-
const Shell = open ? Card : View;
|
|
144
|
-
const shellProps = open
|
|
145
|
-
? { variant: 'outlined' as const, radius: 'radius-16' as const, testID }
|
|
146
|
-
: { testID };
|
|
147
141
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
142
|
+
<Card
|
|
143
|
+
appearance="solid"
|
|
144
|
+
border="none"
|
|
145
|
+
elevation="s"
|
|
146
|
+
radius="radius-16"
|
|
147
|
+
testID={testID}
|
|
150
148
|
style={[open ? { paddingBottom: geo.paddingVertical } : null, style]}
|
|
151
149
|
>
|
|
152
150
|
<View
|
|
@@ -316,6 +314,6 @@ export function MailMessage({
|
|
|
316
314
|
) : null}
|
|
317
315
|
</View>
|
|
318
316
|
) : null}
|
|
319
|
-
</
|
|
317
|
+
</Card>
|
|
320
318
|
);
|
|
321
319
|
}
|
|
@@ -8,6 +8,7 @@ import { MailLabelChips, MailStar } from '../mail-list/parts';
|
|
|
8
8
|
import { mailStrings, resolveMailPaint, visibleLabels } from '../mail-list/shared';
|
|
9
9
|
import { adoptStyleSheet } from '../styles/adopt-style-sheet';
|
|
10
10
|
import { useSurfaceFill } from '../styles/surface-levels';
|
|
11
|
+
import { space } from '../styles/tokens';
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
import { useTheme } from '../theme/use-theme';
|
|
@@ -55,6 +56,7 @@ export function MailThread({
|
|
|
55
56
|
expandedIds,
|
|
56
57
|
onExpandedIdsChange,
|
|
57
58
|
collapseAfter = 4,
|
|
59
|
+
messageGap = space.md,
|
|
58
60
|
quickReply,
|
|
59
61
|
header,
|
|
60
62
|
strings,
|
|
@@ -144,7 +146,7 @@ export function MailThread({
|
|
|
144
146
|
</View>
|
|
145
147
|
) : null}
|
|
146
148
|
<Divider />
|
|
147
|
-
<View style={{ paddingTop:
|
|
149
|
+
<View testID={testID ? `${testID}-messages` : undefined} style={{ paddingTop: space.xs, paddingBottom: space.xs, gap: messageGap }}>
|
|
148
150
|
{entries.map((entry) =>
|
|
149
151
|
entry.kind === 'message' ? (
|
|
150
152
|
<MailMessage
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
|
+
import { RADIUS } from '../design-tokens/scales';
|
|
2
3
|
|
|
3
4
|
import { MAIL_THREAD_MESSAGES } from './messages';
|
|
4
5
|
import type { MailThreadMessage, MailAddress, MailThreadStrings } from './types';
|
|
@@ -44,7 +45,7 @@ export const MAIL_THREAD_GEOMETRY = {
|
|
|
44
45
|
/** The star and the overflow trigger. A touch target, so 44. */
|
|
45
46
|
action: 44,
|
|
46
47
|
/** Radius of a message's own surface. */
|
|
47
|
-
radius:
|
|
48
|
+
radius: RADIUS['radius-16'],
|
|
48
49
|
} as const;
|
|
49
50
|
|
|
50
51
|
/**
|
package/src/mail-thread/types.ts
CHANGED
|
@@ -128,6 +128,8 @@ export interface MailThreadProps {
|
|
|
128
128
|
* Default 4; `0` never collapses.
|
|
129
129
|
*/
|
|
130
130
|
collapseAfter?: number;
|
|
131
|
+
/** Space between message cards and the earlier-messages control. Default space.md (12). */
|
|
132
|
+
messageGap?: number;
|
|
131
133
|
/** A slot under the stack — the quick-reply box, or a row of reply buttons. */
|
|
132
134
|
quickReply?: ReactNode;
|
|
133
135
|
/** A slot above the subject — a back button, the mailbox chrome. */
|