import { useCallback } from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import { useReducedMotion } from '../../../hooks/use-reduced-motion'; import { EASE_OUT } from '../../../lib/ease'; import { MotiView } from '../../../moti/components/view'; import { TIMING_INSTANT } from '../../../theme/motion'; import { OverlayBlur } from './overlay-blur'; import type { OverlayType } from './overlay-type'; /** The backdrop's window-covering frame — negative offsets from the root's measured window position. */ export type OutsidePressFrame = { top: number; left: number; width: number; height: number }; export type OutsidePressBackdropProps = { /** The backdrop's window-covering frame. */ frame: OutsidePressFrame; /** Fold the host on a backdrop tap. Undefined when `closeOnOutsidePress` is off — the layer then only dims. */ onPress?: () => void; /** Fold the host the moment a press lands, before it completes — a touch that * starts outside the pane and drags away still dismisses. */ onPressIn?: () => void; /** The scrim kind: `"blur"` frosts behind a dim, `"opacity"` dims only, `"none"` is transparent. @default 'none' */ overlay?: OverlayType; /** * Whether the blur layer renders inline within the `BlurTarget` it frosts * (the Morphing* panes), which the Android peer cannot do — it degrades to * the dim there. Pass `false` when the backdrop is teleported OUT of the * target through the `BlurProvider` overlay host, so the blur renders. * @default true */ blurInline?: boolean; testID?: string; }; /** * Full-window layer that folds the host on an outside tap and carries the * optional scrim. Rendered inside the small root but measured to cover the * whole window, so a tap anywhere outside the pane — the page, the header, * another control — lands here and dismisses. Sits below the shell, above the * page. * * The dim fades its OWN opacity in on mount and out on exit (the caller * decides whether an exit runs by wrapping this in an `AnimatePresence` — the * MorphingFAB/Switcher backdrops do, so closing un-blurs progressively instead * of popping the scrim off in one frame). The blur is a SIBLING of the dim's * fade wrapper, never a child of it: an ancestor with `opacity < 1` becomes a * CSS "backdrop root" that clips a child's `backdrop-filter` on web, so the * blur layer carries its own opacity fade (see `OverlayBlur`). */ export function OutsidePressBackdrop({ frame, onPress, onPressIn, overlay = 'none', blurInline = true, testID, }: OutsidePressBackdropProps) { const reduce = useReducedMotion(); const handlePress = useCallback(() => onPress?.(), [onPress]); const handlePressIn = useCallback(() => onPressIn?.(), [onPressIn]); const frameStyle = { position: 'absolute' as const, top: frame.top, left: frame.left, width: frame.width, height: frame.height, }; const fade = reduce ? TIMING_INSTANT : { type: 'timing' as const, duration: 200, easing: EASE_OUT }; return ( <> {/* The blur is a sibling behind the dim so its backdrop-filter samples the * page (not the flat dim above it). `blurInline` distinguishes the two * homes: inline (the Morphing* pane's own backdrop, inside the BlurTarget * it blurs on Android) degrades to the plain dim there, while a teleported * backdrop (rendered OUT of the target through the BlurProvider overlay * host) passes `blurInline={false}` so the frost actually renders. * `OverlayBlur` fades its own opacity (0→1 / 1→0) — see the module doc. */} {overlay === 'blur' ? ( ) : null} ); }