| author | |
| committer | |
| log | a29a7cc5853e16d4ca7e07acf364836f71b6101a |
| tree | b7a16c9cedbe4819466b5ef61adb1b09d74b3867 |
| parent | 6c583620ff7ad3235b135af7107c0543467eecfa |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 119 insertions(+), 54 deletions(-)
apps/mobile/src/components/GlassSurface.tsx+34-9| ... | @@ -1,17 +1,20 @@ | ... | @@ -1,17 +1,20 @@ |
| 1 | import { BlurView } from "expo-blur"; | ||
| 1 | import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect"; | 2 | import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect"; |
| 2 | import type { ReactNode, Ref } from "react"; | 3 | import { useContext, type ReactNode, type Ref, type RefObject } from "react"; |
| 3 | import { | 4 | import { |
| 4 | Platform, | 5 | Platform, |
| 6 | StyleSheet, | ||
| 5 | useColorScheme, | 7 | useColorScheme, |
| 6 | View, | 8 | View, |
| 7 | type ColorValue, | 9 | type ColorValue, |
| 8 | type StyleProp, | ||
| 9 | type ViewProps, | 10 | type ViewProps, |
| 10 | type ViewStyle, | 11 | type ViewStyle, |
| 11 | } from "react-native"; | 12 | } from "react-native"; |
| 12 | import { withUniwind } from "uniwind"; | 13 | import { withUniwind } from "uniwind"; |
| 13 | 14 | ||
| 14 | import { cn } from "../lib/cn"; | 15 | import { cn } from "../lib/cn"; |
| 16 | import { GlassBlurTargetContext } from "../lib/glassBlurTarget"; | ||
| 17 | import { themeColorWithAlpha } from "../lib/mobileTheme"; | ||
| 15 | 18 | ||
| 16 | // Explicit mappings keep the native glassEffectStyle enum out of style-array conversion. | 19 | // Explicit mappings keep the native glassEffectStyle enum out of style-array conversion. |
| 17 | const ThemedGlassView = withUniwind(GlassView, { | 20 | const ThemedGlassView = withUniwind(GlassView, { |
| ... | @@ -26,8 +29,9 @@ interface GlassSurfaceProps extends ViewProps { | ... | @@ -26,8 +29,9 @@ interface GlassSurfaceProps extends ViewProps { |
| 26 | readonly tintColor?: ColorValue; | 29 | readonly tintColor?: ColorValue; |
| 27 | readonly tintColorClassName?: string; | 30 | readonly tintColorClassName?: string; |
| 28 | readonly chrome?: "default" | "none"; | 31 | readonly chrome?: "default" | "none"; |
| 29 | /** Styling used only when native Liquid Glass is unavailable. */ | 32 | /** Base color for the frosted tint, or solid fill when blur is unavailable. */ |
| 30 | readonly fallbackStyle?: StyleProp<ViewStyle>; | 33 | readonly fallbackColor?: ColorValue; |
| 34 | readonly blurTarget?: RefObject<View | null>; | ||
| 31 | /** Uniwind styling used only when native Liquid Glass is unavailable. */ | 35 | /** Uniwind styling used only when native Liquid Glass is unavailable. */ |
| 32 | readonly fallbackClassName?: string; | 36 | readonly fallbackClassName?: string; |
| 33 | } | 37 | } |
| ... | @@ -39,13 +43,21 @@ export function GlassSurface({ | ... | @@ -39,13 +43,21 @@ export function GlassSurface({ |
| 39 | chrome = "default", | 43 | chrome = "default", |
| 40 | tintColor, | 44 | tintColor, |
| 41 | tintColorClassName, | 45 | tintColorClassName, |
| 42 | fallbackStyle, | 46 | fallbackColor, |
| 47 | blurTarget, | ||
| 43 | fallbackClassName, | 48 | fallbackClassName, |
| 44 | className, | 49 | className, |
| 45 | style, | 50 | style, |
| 46 | ...props | 51 | ...props |
| 47 | }: GlassSurfaceProps) { | 52 | }: GlassSurfaceProps) { |
| 48 | const isDarkMode = useColorScheme() === "dark"; | 53 | const isDarkMode = useColorScheme() === "dark"; |
| 54 | const inheritedBlurTarget = useContext(GlassBlurTargetContext); | ||
| 55 | const target = blurTarget ?? inheritedBlurTarget; | ||
| 56 | const supportsBlur = | ||
| 57 | Platform.OS === "ios" || | ||
| 58 | (Platform.OS === "android" && Platform.Version >= 31 && target !== undefined); | ||
| 59 | const backgroundColor = | ||
| 60 | fallbackColor === undefined ? undefined : themeColorWithAlpha(String(fallbackColor), 1); | ||
| 49 | const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable(); | 61 | const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable(); |
| 50 | const surfaceStyle: ViewStyle = { | 62 | const surfaceStyle: ViewStyle = { |
| 51 | borderRadius: 32, | 63 | borderRadius: 32, |
| ... | @@ -95,14 +107,27 @@ export function GlassSurface({ | ... | @@ -95,14 +107,27 @@ export function GlassSurface({ |
| 95 | {...props} | 107 | {...props} |
| 96 | ref={ref} | 108 | ref={ref} |
| 97 | className={cn( | 109 | className={cn( |
| 98 | chrome === "none" | 110 | chrome === "none" ? "border-0 border-transparent" : "border border-border", |
| 99 | ? "border-0 border-transparent bg-transparent" | ||
| 100 | : "border border-border bg-glass-surface", | ||
| 101 | fallbackClassName, | 111 | fallbackClassName, |
| 102 | className, | 112 | className, |
| 103 | )} | 113 | )} |
| 104 | style={[surfaceStyle, fallbackStyle, style]} | 114 | style={[surfaceStyle, style]} |
| 105 | > | 115 | > |
| 116 | {supportsBlur ? ( | ||
| 117 | <BlurView | ||
| 118 | pointerEvents="none" | ||
| 119 | blurTarget={target} | ||
| 120 | blurMethod="dimezisBlurViewSdk31Plus" | ||
| 121 | intensity={80} | ||
| 122 | tint={isDarkMode ? "dark" : "default"} | ||
| 123 | style={StyleSheet.absoluteFill} | ||
| 124 | /> | ||
| 125 | ) : null} | ||
| 126 | <View | ||
| 127 | pointerEvents="none" | ||
| 128 | className="absolute inset-0 bg-card" | ||
| 129 | style={{ backgroundColor, opacity: supportsBlur ? (isDarkMode ? 0.25 : 0.55) : 1 }} | ||
| 130 | /> | ||
| 106 | {children} | 131 | {children} |
| 107 | </View> | 132 | </View> |
| 108 | ); | 133 | ); |
apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx+20-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | import { BlurTargetView } from "expo-blur"; | ||
| 1 | import { DEFAULT_TERMINAL_ID, EnvironmentId, ThreadId } from "@t3tools/contracts"; | 2 | import { DEFAULT_TERMINAL_ID, EnvironmentId, ThreadId } from "@t3tools/contracts"; |
| 2 | import { type KnownTerminalSession } from "@t3tools/client-runtime/state/terminal"; | 3 | import { type KnownTerminalSession } from "@t3tools/client-runtime/state/terminal"; |
| 3 | import type { MenuAction } from "@react-native-menu/menu"; | 4 | import type { MenuAction } from "@react-native-menu/menu"; |
| ... | @@ -154,6 +155,7 @@ type ThreadTerminalRouteScreenProps = StaticScreenProps<{ | ... | @@ -154,6 +155,7 @@ type ThreadTerminalRouteScreenProps = StaticScreenProps<{ |
| 154 | }>; | 155 | }>; |
| 155 | 156 | ||
| 156 | export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) { | 157 | export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) { |
| 158 | const terminalBlurTarget = useRef<View>(null); | ||
| 157 | const navigation = useNavigation(); | 159 | const navigation = useNavigation(); |
| 158 | const writeTerminal = useAtomCommand(terminalEnvironment.write, "terminal write"); | 160 | const writeTerminal = useAtomCommand(terminalEnvironment.write, "terminal write"); |
| 159 | const resizeTerminal = useAtomCommand(terminalEnvironment.resize, "terminal resize"); | 161 | const resizeTerminal = useAtomCommand(terminalEnvironment.resize, "terminal resize"); |
| ... | @@ -1260,7 +1262,21 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) | ... | @@ -1260,7 +1262,21 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) |
| 1260 | /> | 1262 | /> |
| 1261 | ) : ( | 1263 | ) : ( |
| 1262 | <> | 1264 | <> |
| 1263 | <View className="flex-1" style={{ paddingBottom: terminalBottomInset }}> | 1265 | <BlurTargetView |
| 1266 | ref={terminalBlurTarget} | ||
| 1267 | style={{ | ||
| 1268 | flex: 1, | ||
| 1269 | paddingBottom: terminalBottomInset, | ||
| 1270 | }} | ||
| 1271 | > | ||
| 1272 | <View | ||
| 1273 | pointerEvents="none" | ||
| 1274 | style={{ | ||
| 1275 | position: "absolute", | ||
| 1276 | inset: 0, | ||
| 1277 | backgroundColor: terminalTheme.background, | ||
| 1278 | }} | ||
| 1279 | /> | ||
| 1264 | <TerminalSurface | 1280 | <TerminalSurface |
| 1265 | autoFocus={!SHOWCASE_ENABLED} | 1281 | autoFocus={!SHOWCASE_ENABLED} |
| 1266 | buffer={terminalSurfaceBuffer} | 1282 | buffer={terminalSurfaceBuffer} |
| ... | @@ -1273,7 +1289,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) | ... | @@ -1273,7 +1289,7 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) |
| 1273 | terminalKey={terminalKey} | 1289 | terminalKey={terminalKey} |
| 1274 | theme={terminalTheme} | 1290 | theme={terminalTheme} |
| 1275 | /> | 1291 | /> |
| 1276 | </View> | 1292 | </BlurTargetView> |
| 1277 | 1293 | ||
| 1278 | {isAccessoryVisible ? ( | 1294 | {isAccessoryVisible ? ( |
| 1279 | <KeyboardStickyView | 1295 | <KeyboardStickyView |
| ... | @@ -1340,6 +1356,8 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) | ... | @@ -1340,6 +1356,8 @@ export function ThreadTerminalRouteScreen(props: ThreadTerminalRouteScreenProps) |
| 1340 | > | 1356 | > |
| 1341 | <GlassSurface | 1357 | <GlassSurface |
| 1342 | chrome="none" | 1358 | chrome="none" |
| 1359 | blurTarget={terminalBlurTarget} | ||
| 1360 | fallbackColor={terminalTheme.background} | ||
| 1343 | glassEffectStyle="regular" | 1361 | glassEffectStyle="regular" |
| 1344 | tintColor="transparent" | 1362 | tintColor="transparent" |
| 1345 | style={{ | 1363 | style={{ |
apps/mobile/src/features/threads/ThreadComposer.tsx+6-4| ... | @@ -143,7 +143,7 @@ export interface ThreadComposerProps { | ... | @@ -143,7 +143,7 @@ export interface ThreadComposerProps { |
| 143 | 143 | ||
| 144 | /** | 144 | /** |
| 145 | * The pill / card container — renders with Expo's native GlassView on supported | 145 | * The pill / card container — renders with Expo's native GlassView on supported |
| 146 | * iOS 26+ devices and keeps the existing opaque fallback elsewhere. | 146 | * iOS 26+ devices, with a frosted blur fallback where supported. |
| 147 | * Exported so NewTaskDraftScreen can render the same composer chrome. | 147 | * Exported so NewTaskDraftScreen can render the same composer chrome. |
| 148 | */ | 148 | */ |
| 149 | // The bottom-anchored dock position and clipped surface height use the same | 149 | // The bottom-anchored dock position and clipped surface height use the same |
| ... | @@ -172,6 +172,7 @@ export function ComposerSurface(props: { | ... | @@ -172,6 +172,7 @@ export function ComposerSurface(props: { |
| 172 | readonly animateLayout?: boolean; | 172 | readonly animateLayout?: boolean; |
| 173 | }) { | 173 | }) { |
| 174 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); | 174 | const { materialYouStyleLayoutActive } = useAppearancePreferences(); |
| 175 | const colors = useUniwindTheme(); | ||
| 175 | const targetBorderRadius = | 176 | const targetBorderRadius = |
| 176 | typeof props.style.borderRadius === "number" ? props.style.borderRadius : 0; | 177 | typeof props.style.borderRadius === "number" ? props.style.borderRadius : 0; |
| 177 | const animatedBorderRadius = useSharedValue(targetBorderRadius); | 178 | const animatedBorderRadius = useSharedValue(targetBorderRadius); |
| ... | @@ -210,10 +211,11 @@ export function ComposerSurface(props: { | ... | @@ -210,10 +211,11 @@ export function ComposerSurface(props: { |
| 210 | > | 211 | > |
| 211 | <AnimatedGlassSurface | 212 | <AnimatedGlassSurface |
| 212 | chrome="none" | 213 | chrome="none" |
| 214 | fallbackColor={ | ||
| 215 | materialYouStyleLayoutActive ? colors["--color-composer-surface"] : colors["--color-card"] | ||
| 216 | } | ||
| 213 | fallbackClassName={ | 217 | fallbackClassName={ |
| 214 | materialYouStyleLayoutActive | 218 | materialYouStyleLayoutActive ? "border border-composer-border" : "border border-border" |
| 215 | ? "border border-composer-border bg-composer-surface" | ||
| 216 | : "border border-border bg-card-translucent" | ||
| 217 | } | 219 | } |
| 218 | glassEffectStyle="regular" | 220 | glassEffectStyle="regular" |
| 219 | // The composer is a passive material containing interactive controls. | 221 | // The composer is a passive material containing interactive controls. |
apps/mobile/src/features/threads/ThreadDetailScreen.tsx+53-39| ... | @@ -26,6 +26,8 @@ import type { | ... | @@ -26,6 +26,8 @@ import type { |
| 26 | UserInputQuestion, | 26 | UserInputQuestion, |
| 27 | } from "@t3tools/contracts"; | 27 | } from "@t3tools/contracts"; |
| 28 | import * as Haptics from "expo-haptics"; | 28 | import * as Haptics from "expo-haptics"; |
| 29 | import { BlurTargetView } from "expo-blur"; | ||
| 30 | import { GlassBlurTargetContext } from "../../lib/glassBlurTarget"; | ||
| 29 | import { | 31 | import { |
| 30 | memo, | 32 | memo, |
| 31 | useCallback, | 33 | useCallback, |
| ... | @@ -807,7 +809,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -807,7 +809,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 807 | }, [freeze, scrollMessageToEnd]); | 809 | }, [freeze, scrollMessageToEnd]); |
| 808 | 810 | ||
| 809 | const showScrollToEndButton = contentPresentationKind === "ready" && !endFollowEnabled; | 811 | const showScrollToEndButton = contentPresentationKind === "ready" && !endFollowEnabled; |
| 810 | const { themeAppearance } = useAppearancePreferences(); | 812 | const { themeAppearance, materialYouStyleLayoutActive } = useAppearancePreferences(); |
| 811 | const isDarkMode = themeAppearance === "dark"; | 813 | const isDarkMode = themeAppearance === "dark"; |
| 812 | 814 | ||
| 813 | const handleFeedTouchStart = useCallback((event: GestureResponderEvent) => { | 815 | const handleFeedTouchStart = useCallback((event: GestureResponderEvent) => { |
| ... | @@ -839,17 +841,27 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -839,17 +841,27 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 839 | const handleFeedTouchCancel = useCallback(() => { | 841 | const handleFeedTouchCancel = useCallback(() => { |
| 840 | feedTouchStartRef.current = null; | 842 | feedTouchStartRef.current = null; |
| 841 | }, []); | 843 | }, []); |
| 844 | const feedBlurTarget = useRef<View>(null); | ||
| 842 | 845 | ||
| 843 | return ( | 846 | return ( |
| 844 | <View className="flex-1"> | 847 | <View className="flex-1"> |
| 845 | {showContent ? ( | 848 | {showContent ? ( |
| 846 | <View | 849 | <BlurTargetView |
| 847 | className="flex-1" | 850 | ref={feedBlurTarget} |
| 851 | style={{ flex: 1 }} | ||
| 848 | onTouchStart={handleFeedTouchStart} | 852 | onTouchStart={handleFeedTouchStart} |
| 849 | onTouchMove={handleFeedTouchMove} | 853 | onTouchMove={handleFeedTouchMove} |
| 850 | onTouchEnd={handleFeedTouchEnd} | 854 | onTouchEnd={handleFeedTouchEnd} |
| 851 | onTouchCancel={handleFeedTouchCancel} | 855 | onTouchCancel={handleFeedTouchCancel} |
| 852 | > | 856 | > |
| 857 | <View | ||
| 858 | pointerEvents="none" | ||
| 859 | className={ | ||
| 860 | materialYouStyleLayoutActive | ||
| 861 | ? "absolute inset-0 bg-thread-canvas" | ||
| 862 | : "absolute inset-0 bg-screen" | ||
| 863 | } | ||
| 864 | /> | ||
| 853 | <ThreadFeed | 865 | <ThreadFeed |
| 854 | key={selectedThreadKey} | 866 | key={selectedThreadKey} |
| 855 | environmentId={props.environmentId} | 867 | environmentId={props.environmentId} |
| ... | @@ -881,7 +893,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -881,7 +893,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 881 | onUseArtifactTemplate={handleUseArtifactTemplate} | 893 | onUseArtifactTemplate={handleUseArtifactTemplate} |
| 882 | loadEarlier={props.loadEarlier ?? null} | 894 | loadEarlier={props.loadEarlier ?? null} |
| 883 | /> | 895 | /> |
| 884 | </View> | 896 | </BlurTargetView> |
| 885 | ) : ( | 897 | ) : ( |
| 886 | <View className="flex-1" /> | 898 | <View className="flex-1" /> |
| 887 | )} | 899 | )} |
| ... | @@ -1003,41 +1015,43 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -1003,41 +1015,43 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 1003 | : undefined | 1015 | : undefined |
| 1004 | } | 1016 | } |
| 1005 | > | 1017 | > |
| 1006 | <ThreadComposer | 1018 | <GlassBlurTargetContext value={feedBlurTarget}> |
| 1007 | editorRef={composerEditorRef} | 1019 | <ThreadComposer |
| 1008 | draftMessage={props.draftMessage} | 1020 | editorRef={composerEditorRef} |
| 1009 | draftAttachments={props.draftAttachments} | 1021 | draftMessage={props.draftMessage} |
| 1010 | placeholder="Ask the repo agent, or run a command…" | 1022 | draftAttachments={props.draftAttachments} |
| 1011 | contentMaxWidth={contentMaxWidth} | 1023 | placeholder="Ask the repo agent, or run a command…" |
| 1012 | connectionState={props.connectionStateLabel} | 1024 | contentMaxWidth={contentMaxWidth} |
| 1013 | environmentLabel={props.environmentLabel} | 1025 | connectionState={props.connectionStateLabel} |
| 1014 | selectedThread={props.selectedThread} | 1026 | environmentLabel={props.environmentLabel} |
| 1015 | hasCompactableConversation={hasCompactableConversation && !props.isCompacting} | 1027 | selectedThread={props.selectedThread} |
| 1016 | serverConfig={props.serverConfig} | 1028 | hasCompactableConversation={hasCompactableConversation && !props.isCompacting} |
| 1017 | queueCount={props.selectedThreadQueueCount} | 1029 | serverConfig={props.serverConfig} |
| 1018 | environmentId={props.environmentId} | 1030 | queueCount={props.selectedThreadQueueCount} |
| 1019 | projectCwd={props.threadCwd ?? props.projectWorkspaceRoot} | 1031 | environmentId={props.environmentId} |
| 1020 | // Follow-ups typed during setup wait in the draft: queueing | 1032 | projectCwd={props.threadCwd ?? props.projectWorkspaceRoot} |
| 1021 | // them against a thread id the server may still reject | 1033 | // Follow-ups typed during setup wait in the draft: queueing |
| 1022 | // would strand them in the outbox. | 1034 | // them against a thread id the server may still reject |
| 1023 | sendBlockedReason={ | 1035 | // would strand them in the outbox. |
| 1024 | props.creationState?.kind === "preparing" ? "Starting the task…" : null | 1036 | sendBlockedReason={ |
| 1025 | } | 1037 | props.creationState?.kind === "preparing" ? "Starting the task…" : null |
| 1026 | bottomInset={composerBottomInset} | 1038 | } |
| 1027 | onChangeDraftMessage={props.onChangeDraftMessage} | 1039 | bottomInset={composerBottomInset} |
| 1028 | onPickDraftMedia={props.onPickDraftMedia} | 1040 | onChangeDraftMessage={props.onChangeDraftMessage} |
| 1029 | onPickDraftFiles={props.onPickDraftFiles} | 1041 | onPickDraftMedia={props.onPickDraftMedia} |
| 1030 | onNativePasteImages={props.onNativePasteImages} | 1042 | onPickDraftFiles={props.onPickDraftFiles} |
| 1031 | onRemoveDraftImage={props.onRemoveDraftImage} | 1043 | onNativePasteImages={props.onNativePasteImages} |
| 1032 | onStopThread={props.onStopThread} | 1044 | onRemoveDraftImage={props.onRemoveDraftImage} |
| 1033 | onSendMessage={handleSendMessage} | 1045 | onStopThread={props.onStopThread} |
| 1034 | onShowUsageLimits={showUsageLimits} | 1046 | onSendMessage={handleSendMessage} |
| 1035 | onUpdateModelSelection={props.onUpdateThreadModelSelection} | 1047 | onShowUsageLimits={showUsageLimits} |
| 1036 | onUpdateRuntimeMode={props.onUpdateThreadRuntimeMode} | 1048 | onUpdateModelSelection={props.onUpdateThreadModelSelection} |
| 1037 | onUpdateInteractionMode={props.onUpdateThreadInteractionMode} | 1049 | onUpdateRuntimeMode={props.onUpdateThreadRuntimeMode} |
| 1038 | onExpandedChange={setComposerExpanded} | 1050 | onUpdateInteractionMode={props.onUpdateThreadInteractionMode} |
| 1039 | onEditorFocusChange={handleComposerFocusChange} | 1051 | onExpandedChange={setComposerExpanded} |
| 1040 | /> | 1052 | onEditorFocusChange={handleComposerFocusChange} |
| 1053 | /> | ||
| 1054 | </GlassBlurTargetContext> | ||
| 1041 | </View> | 1055 | </View> |
| 1042 | </View> | 1056 | </View> |
| 1043 | </Animated.View> | 1057 | </Animated.View> |
apps/mobile/src/lib/glassBlurTarget.ts created+6| ... | @@ -0,0 +1,6 @@ | ||
| 1 | import { createContext, type RefObject } from "react"; | ||
| 2 | import type { View } from "react-native"; | ||
| 3 | |||
| 4 | // Android cannot sample a target that contains the BlurView itself. Keep the | ||
| 5 | // feed in a separate target, shared by the composer and its popovers. | ||
| 6 | export const GlassBlurTargetContext = createContext<RefObject<View | null> | undefined>(undefined); | ||