@oxy.so/bloom 7.1.5 → 7.2.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 +12 -11
- package/docs/app-shell.mdx +26 -2
- package/docs/appearance.mdx +16 -1
- package/docs/content-panel.mdx +4 -0
- package/docs/mail-thread.mdx +8 -0
- package/docs/page-footer.mdx +60 -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/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/content-panel/ContentPanel.js +34 -23
- package/lib/commonjs/content-panel/ContentPanel.js.map +1 -1
- package/lib/commonjs/content-panel/ContentPanel.web.js +39 -17
- 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/index.js +939 -927
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +939 -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/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/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/content-panel/ContentPanel.js +35 -24
- package/lib/module/content-panel/ContentPanel.js.map +1 -1
- package/lib/module/content-panel/ContentPanel.web.js +41 -19
- 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/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- 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/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/types.d.ts +11 -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/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 +2 -2
- package/lib/typescript/commonjs/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +1 -0
- 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/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/types.d.ts +11 -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/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 +2 -2
- package/lib/typescript/module/design-tokens/shapes.d.ts +2 -2
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +1 -0
- 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/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/types.ts +11 -3
- package/src/appearance/BloomScope.tsx +5 -2
- package/src/appearance/context.ts +2 -2
- package/src/appearance/types.ts +4 -1
- package/src/content-panel/ContentPanel.tsx +32 -25
- package/src/content-panel/ContentPanel.web.tsx +32 -23
- package/src/content-panel/types.ts +2 -2
- package/src/design-tokens/shapes.ts +1 -1
- package/src/design-tokens/tokens.json +2 -2
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- 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/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
|
@@ -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": {
|
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';
|
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';
|
|
@@ -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. */
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { memo, useContext, useEffect, useId, useState } from 'react';
|
|
2
|
+
import { Platform, StyleSheet, View, useWindowDimensions, type LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
|
4
|
+
import { BloomScope } from '../appearance';
|
|
5
|
+
import { useBottomEdgeInset } from '../layout/bottom-edge';
|
|
6
|
+
import { EdgeScrim, SCRIM_TAIL_RATIO } from '../page-header/EdgeScrim';
|
|
7
|
+
import { BREAKPOINTS } from '../styles/breakpoints';
|
|
8
|
+
import { useSurfaceFill } from '../styles/surface-levels';
|
|
9
|
+
import { Z_INDEX } from '../styles/z-index';
|
|
10
|
+
import { useTheme } from '../theme/use-theme';
|
|
11
|
+
import { PageFooterContext } from './context';
|
|
12
|
+
import type { PageFooterProps } from './types';
|
|
13
|
+
|
|
14
|
+
const BAR_HEIGHT = 56;
|
|
15
|
+
|
|
16
|
+
/** Page-local controls floating over a bounded scroller, above app navigation. */
|
|
17
|
+
function PageFooterComponent({
|
|
18
|
+
children, actions, bottomInset, safeArea, scrim = 'always', scrimColor, style, testID,
|
|
19
|
+
}: PageFooterProps) {
|
|
20
|
+
const theme = useTheme();
|
|
21
|
+
const fill = useSurfaceFill() ?? theme.colors.background;
|
|
22
|
+
const occupied = useBottomEdgeInset();
|
|
23
|
+
const rawBottom = bottomInset ?? occupied;
|
|
24
|
+
const bottom = Number.isFinite(rawBottom) ? Math.max(0, rawBottom) : 0;
|
|
25
|
+
const insets = useContext(SafeAreaInsetsContext);
|
|
26
|
+
const padBottom = (safeArea ?? Platform.OS !== 'web') && bottom === 0 ? insets?.bottom ?? 0 : 0;
|
|
27
|
+
const windowWidth = useWindowDimensions().width;
|
|
28
|
+
const [width, setWidth] = useState<number | null>(null);
|
|
29
|
+
// Measure the row, not the outer box: a safe-area change can then update the
|
|
30
|
+
// published clearance immediately rather than waiting for another onLayout.
|
|
31
|
+
const [rowHeight, setRowHeight] = useState(BAR_HEIGHT);
|
|
32
|
+
const height = rowHeight + padBottom;
|
|
33
|
+
const sideInset = (width ?? windowWidth) >= BREAKPOINTS.sm ? 24 : 16;
|
|
34
|
+
const store = useContext(PageFooterContext);
|
|
35
|
+
const id = useId();
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
store?.claim(id, height + bottom);
|
|
38
|
+
return () => store?.release(id);
|
|
39
|
+
}, [store, id, height, bottom]);
|
|
40
|
+
|
|
41
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
42
|
+
const layout = event.nativeEvent.layout;
|
|
43
|
+
if (Number.isFinite(layout.width)) setWidth(Math.max(0, layout.width));
|
|
44
|
+
if (Number.isFinite(layout.height)) setRowHeight(Math.max(0, layout.height));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<View
|
|
49
|
+
testID={testID}
|
|
50
|
+
pointerEvents="box-none"
|
|
51
|
+
style={[styles.container, { bottom, paddingBottom: padBottom }, style]}
|
|
52
|
+
>
|
|
53
|
+
{scrim !== 'none' ? (
|
|
54
|
+
<View
|
|
55
|
+
pointerEvents="none"
|
|
56
|
+
style={[styles.scrim, { height: height * (1 + SCRIM_TAIL_RATIO) }]}
|
|
57
|
+
testID={testID ? `${testID}-scrim` : undefined}
|
|
58
|
+
>
|
|
59
|
+
<EdgeScrim edge="bottom" color={scrimColor ?? fill} testID={testID ? `${testID}-scrim-gradient` : undefined} />
|
|
60
|
+
</View>
|
|
61
|
+
) : null}
|
|
62
|
+
<View
|
|
63
|
+
onLayout={onLayout}
|
|
64
|
+
pointerEvents="box-none"
|
|
65
|
+
style={[styles.row, { paddingLeft: sideInset, paddingRight: sideInset }]}
|
|
66
|
+
testID={testID ? `${testID}-row` : undefined}
|
|
67
|
+
>
|
|
68
|
+
<BloomScope>
|
|
69
|
+
{children != null ? <View pointerEvents="box-none" style={styles.content}>{children}</View> : null}
|
|
70
|
+
{actions != null ? (
|
|
71
|
+
<View pointerEvents="box-none" style={styles.actions} testID={testID ? `${testID}-actions` : undefined}>
|
|
72
|
+
{actions}
|
|
73
|
+
</View>
|
|
74
|
+
) : null}
|
|
75
|
+
</BloomScope>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const styles = StyleSheet.create({
|
|
82
|
+
container: {
|
|
83
|
+
position: 'absolute', left: 0, right: 0, zIndex: Z_INDEX.floating,
|
|
84
|
+
...(Platform.OS !== 'web' ? { elevation: Z_INDEX.floating } : {}),
|
|
85
|
+
},
|
|
86
|
+
scrim: { position: 'absolute', bottom: 0, left: 0, right: 0 },
|
|
87
|
+
row: {
|
|
88
|
+
minHeight: BAR_HEIGHT, flexDirection: 'row', alignItems: 'center',
|
|
89
|
+
gap: 8, paddingTop: 8, paddingBottom: 8,
|
|
90
|
+
},
|
|
91
|
+
content: { flex: 1, minWidth: 0 },
|
|
92
|
+
actions: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 8, flexShrink: 1 },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const PageFooter = memo(PageFooterComponent);
|
|
96
|
+
PageFooter.displayName = 'PageFooter';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React, { useState, type PropsWithChildren } from 'react';
|
|
2
|
+
import { createEdgeStore } from '../layout/edge-store';
|
|
3
|
+
import { PageFooterContext } from './context';
|
|
4
|
+
|
|
5
|
+
/** Scope a page's footer measurement to its sibling scroller. Adds no layout. */
|
|
6
|
+
export function PageFooterProvider({ children }: PropsWithChildren) {
|
|
7
|
+
const [store] = useState(createEdgeStore);
|
|
8
|
+
return <PageFooterContext.Provider value={store}>{children}</PageFooterContext.Provider>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import type { EdgeStore } from '../layout/edge-store';
|
|
3
|
+
|
|
4
|
+
// Local scroll clearance, deliberately separate from the global bottom edge
|
|
5
|
+
// that positions this footer. Publishing there would make it avoid itself.
|
|
6
|
+
export const PageFooterContext = createContext<EdgeStore | null>(null);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface PageFooterProps {
|
|
5
|
+
/** Optional content before the actions, for example a selection summary. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
/** Controls grouped by the caller; inherits the surrounding Bloom size. */
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Distance from the containing frame's bottom. Defaults to the occupied
|
|
11
|
+
* bottom edge (for example AppShell's measured bottom bar). Pass 0 when the
|
|
12
|
+
* frame already ends above navigation, or the footer is in a desktop pane.
|
|
13
|
+
*/
|
|
14
|
+
bottomInset?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Include the bottom safe area when no bottom edge is occupied. Defaults to
|
|
17
|
+
* true on native, false on web. Disable when the containing shell owns it.
|
|
18
|
+
*/
|
|
19
|
+
safeArea?: boolean;
|
|
20
|
+
/** The PageHeader gradient, mirrored upward. Default always. */
|
|
21
|
+
scrim?: 'always' | 'none';
|
|
22
|
+
/** Opaque gradient color; defaults to the containing surface's fill. */
|
|
23
|
+
scrimColor?: string;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
testID?: string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useContext, useSyncExternalStore } from 'react';
|
|
2
|
+
import { NO_INSET, NO_SUBSCRIPTION } from '../layout/edge-store';
|
|
3
|
+
import { PageFooterContext } from './context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Clearance for the last scroll item: measured footer height plus bottomInset.
|
|
7
|
+
* Use as contentContainerStyle.paddingBottom. Returns 0 without a provider or
|
|
8
|
+
* once the footer unmounts; does not include the decorative gradient tail.
|
|
9
|
+
*/
|
|
10
|
+
export function usePageFooterInset(): number {
|
|
11
|
+
const store = useContext(PageFooterContext);
|
|
12
|
+
return useSyncExternalStore(
|
|
13
|
+
store?.subscribe ?? NO_SUBSCRIPTION,
|
|
14
|
+
store?.getInset ?? NO_INSET,
|
|
15
|
+
store?.getInset ?? NO_INSET,
|
|
16
|
+
);
|
|
17
|
+
}
|
package/src/sidebar/Sidebar.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveSurfaceMaterial } from '../surface/resolve-surface-material';
|
|
|
3
3
|
import { SurfacePaint } from '../surface/SurfacePaint';
|
|
4
4
|
import { parseRgba } from '../theme/color-utils';
|
|
5
5
|
import { surfaceStyle } from '../shapes/surface-style';
|
|
6
|
-
import {
|
|
6
|
+
import { usePanelShape } from '../styles/panel-chrome';
|
|
7
7
|
import { useBloomAppearance } from '../appearance';
|
|
8
8
|
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
9
9
|
import {
|
|
@@ -62,7 +62,7 @@ import { SIDEBAR_MESSAGES } from './messages';
|
|
|
62
62
|
* panel `size` wide expanded (p12), its collapsed width at px7 py12 —
|
|
63
63
|
* with the 1px border that leaves exactly the size's item column
|
|
64
64
|
* (`metrics.ts`; `md` is the historical 260 / 52 / 36)
|
|
65
|
-
* surface `card` radius
|
|
65
|
+
* surface `card` shared panel radius, 1px border-button-white, shadow-sidebar,
|
|
66
66
|
* background-secondary; `plain` drops the chrome onto
|
|
67
67
|
* background-full; `docked` keeps the fill, squares the corners
|
|
68
68
|
* and leaves one hairline on the inner edge, full height
|
|
@@ -219,6 +219,7 @@ const SidebarPanel: React.FC<SidebarProps> = ({
|
|
|
219
219
|
const palette = useSidebarPalette();
|
|
220
220
|
const parentFill = useSurfaceFill();
|
|
221
221
|
const parentLevel = useSurfaceLevelValue();
|
|
222
|
+
const panelShape = usePanelShape();
|
|
222
223
|
const customSurface = StyleSheet.flatten(style);
|
|
223
224
|
const surfaceFill = customSurface?.backgroundColor ?? (surface === 'plain' ? undefined : (palette.panel));
|
|
224
225
|
const hasFill = typeof surfaceFill === 'string' && surfaceFill !== 'transparent' && parseRgba(surfaceFill)?.a !== 0;
|
|
@@ -444,7 +445,7 @@ const SidebarPanel: React.FC<SidebarProps> = ({
|
|
|
444
445
|
borderEndColor: palette.dockedEdge,
|
|
445
446
|
}
|
|
446
447
|
: {
|
|
447
|
-
...surfaceStyle(
|
|
448
|
+
...surfaceStyle(panelShape),
|
|
448
449
|
borderWidth: 1,
|
|
449
450
|
borderColor: palette.panelBorder,
|
|
450
451
|
backgroundColor: 'transparent',
|
|
@@ -709,7 +710,7 @@ const SidebarPanel: React.FC<SidebarProps> = ({
|
|
|
709
710
|
surfaceFillVars(publishedFill),
|
|
710
711
|
]}
|
|
711
712
|
>
|
|
712
|
-
{paintsSurface ? <SurfacePaint fill={surfaceFill} shape={{ curve:
|
|
713
|
+
{paintsSurface ? <SurfacePaint fill={surfaceFill} shape={{ curve: panelShape.curve }} radius={customSurface?.borderRadius ?? (surface === 'card' ? panelShape.radius : 0)} /> : null}
|
|
713
714
|
<View testID={`${testID ?? 'sidebar'}-main-region`} style={{ width: '100%', minHeight: 0, flexShrink: 1, ...(contentAlignment === 'center' ? { flex: 1, justifyContent: 'center' as const } : {}) }}>
|
|
714
715
|
<View style={{ width: '100%', minHeight: 0, flexShrink: 1 }}>
|
|
715
716
|
{/* Fixed chrome shares the panel's morph, never the destination scroll.
|
package/src/sidebar/types.ts
CHANGED
|
@@ -151,7 +151,7 @@ export type SidebarVariant = 'panel' | 'rail';
|
|
|
151
151
|
* page, independent of what it contains. It is the axis two apps differ on
|
|
152
152
|
* most: the same rows read as a different product behind a different edge.
|
|
153
153
|
*
|
|
154
|
-
* - `card` (default) the floating panel: radius
|
|
154
|
+
* - `card` (default) the floating panel: shared panel radius (28 by default), a hairline and the panel
|
|
155
155
|
* shadow, on the panel fill. A sidebar that sits IN the page.
|
|
156
156
|
* - `plain` no edge at all — no radius, no border, no shadow: the rows sit
|
|
157
157
|
* straight on the page. The social/reader look, and what a revealed mobile
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The FLOATING PANEL chrome — the edge Bloom's big surfaces wear: a hairline
|
|
3
|
-
* plus the soft three-part shadow that lifts a panel off the page background.
|
|
3
|
+
* plus the shared corner shape and soft three-part shadow that lifts a panel off the page background.
|
|
4
4
|
*
|
|
5
5
|
* It lives here because two surfaces wear the same thing and had it written
|
|
6
6
|
* twice: the `Sidebar`'s panel and the framed `ContentPanel`. A panel that only
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* border the canonical subtle border role in both modes
|
|
13
13
|
*/
|
|
14
14
|
import { useContext } from 'react';
|
|
15
|
+
import { BloomAppearanceContext } from '../appearance/context';
|
|
16
|
+
import { SURFACE_SHAPES } from '../design-tokens/shapes';
|
|
15
17
|
|
|
16
18
|
import { BloomThemeContext } from '../theme/BloomThemeProvider';
|
|
17
19
|
import type { Theme } from '../theme/types';
|
|
@@ -51,3 +53,9 @@ export function useOptionalPanelChrome(): PanelChrome | null {
|
|
|
51
53
|
const ctx = useContext(BloomThemeContext);
|
|
52
54
|
return ctx ? resolvePanelChrome(ctx.theme) : null;
|
|
53
55
|
}
|
|
56
|
+
|
|
57
|
+
/** Shared panel geometry, independently configurable from control size. */
|
|
58
|
+
export function usePanelShape() {
|
|
59
|
+
const { panelRadius } = useContext(BloomAppearanceContext);
|
|
60
|
+
return { ...SURFACE_SHAPES.sidebar, radius: panelRadius ?? SURFACE_SHAPES.sidebar.radius };
|
|
61
|
+
}
|