react-native-quick-preview 1.0.4 → 2.0.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/package.json CHANGED
@@ -1,81 +1,103 @@
1
- {
2
- "name": "react-native-quick-preview",
3
- "version": "1.0.4",
4
- "description": "A beautiful, customizable quick preview modal component for React Native",
5
- "author": "Oliver Lindblad",
6
- "license": "MIT",
7
-
8
- "main": "dist/index.js",
9
- "module": "dist/index.mjs",
10
- "types": "dist/index.d.ts",
11
- "react-native": "dist/index.js",
12
-
13
- "exports": {
14
- ".": {
15
- "types": "./dist/index.d.ts",
16
- "react-native": "./dist/index.js",
17
- "import": "./dist/index.mjs",
18
- "require": "./dist/index.js",
19
- "default": "./dist/index.js"
20
- },
21
- "./package.json": "./package.json"
22
- },
23
-
24
- "sideEffects": false,
25
- "files": ["dist", "src", "README.md", "LICENSE"],
26
-
27
- "scripts": {
28
- "build": "tsup",
29
- "dev": "tsup --watch",
30
- "type-check": "tsc --noEmit",
31
- "lint": "eslint src --ext .ts,.tsx",
32
- "clean": "rimraf dist",
33
- "prepublishOnly": "rimraf dist && tsup"
34
- },
35
-
36
- "keywords": [
37
- "react-native",
38
- "modal",
39
- "preview",
40
- "quicklook",
41
- "expo",
42
- "typescript",
43
- "ui",
44
- "component"
45
- ],
46
-
47
- "peerDependencies": {
48
- "react": ">=18.2.0 <21",
49
- "react-native": ">=0.64.0 <1"
50
- },
51
-
52
- "devDependencies": {
53
- "@types/react": "^19.0.0",
54
- "@typescript-eslint/eslint-plugin": "^7.18.0",
55
- "@typescript-eslint/parser": "^7.18.0",
56
- "eslint": "^8.57.0",
57
- "rimraf": "^6.0.0",
58
- "tsup": "^8.0.0",
59
- "typescript": "^5.0.0",
60
-
61
- "react": "19.0.0",
62
- "react-native": "0.79.5"
63
- },
64
-
65
- "overrides": {
66
- "eslint": "^8.57.0",
67
- "@typescript-eslint/parser": "^7.18.0",
68
- "@typescript-eslint/eslint-plugin": "^7.18.0"
69
- },
70
-
71
- "repository": {
72
- "type": "git",
73
- "url": "https://github.com/Hashtagsmile/react-native-quicklook.git"
74
- },
75
- "bugs": {
76
- "url": "https://github.com/Hashtagsmile/react-native-quicklook/issues"
77
- },
78
- "homepage": "https://github.com/Hashtagsmile/react-native-quicklook#readme",
79
- "publishConfig": { "access": "public" },
80
- "engines": { "node": ">=16" }
81
- }
1
+ {
2
+ "name": "react-native-quick-preview",
3
+ "version": "2.0.0",
4
+ "description": "Headless long-press quick preview for React Native. Peek at any content in a popover or sheet, swipe down to dismiss.",
5
+ "author": "Oliver Lindblad",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "react-native": "src/index.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "react-native": "./src/index.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "sideEffects": false,
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "!src/**/__tests__/**",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "dev": "tsup --watch",
32
+ "type-check": "tsc --noEmit",
33
+ "lint": "eslint src --ext .ts,.tsx",
34
+ "clean": "rimraf dist",
35
+ "prepublishOnly": "rimraf dist && tsup",
36
+ "test": "jest"
37
+ },
38
+ "keywords": [
39
+ "react-native",
40
+ "expo",
41
+ "modal",
42
+ "preview",
43
+ "quick-preview",
44
+ "quick-view",
45
+ "peek",
46
+ "long-press",
47
+ "bottom-sheet",
48
+ "popover",
49
+ "headless",
50
+ "typescript",
51
+ "ui",
52
+ "component"
53
+ ],
54
+ "peerDependencies": {
55
+ "react": ">=18.2.0 <21",
56
+ "react-native": ">=0.64.0 <1",
57
+ "react-native-gesture-handler": ">=2.0.0",
58
+ "react-native-portalize": ">=1.0.0",
59
+ "react-native-reanimated": ">=3.0.0",
60
+ "react-native-safe-area-context": ">=4.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@react-native/babel-preset": "0.81.5",
64
+ "@testing-library/react-native": "^13.3.3",
65
+ "@types/jest": "^29.5.14",
66
+ "@types/react": "^19.0.0",
67
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
68
+ "@typescript-eslint/parser": "^7.18.0",
69
+ "eslint": "^8.57.0",
70
+ "jest": "^29.7.0",
71
+ "react": "19.1.0",
72
+ "react-native": "0.81.5",
73
+ "react-native-gesture-handler": "~2.28.0",
74
+ "react-native-portalize": "^1.0.0",
75
+ "react-native-reanimated": "~4.1.1",
76
+ "react-native-safe-area-context": "~5.6.0",
77
+ "react-test-renderer": "19.1.0",
78
+ "rimraf": "^6.0.0",
79
+ "tsup": "^8.0.0",
80
+ "typescript": "^5.0.0",
81
+ "react-native-worklets": "~0.5.1"
82
+ },
83
+ "overrides": {
84
+ "eslint": "^8.57.0",
85
+ "@typescript-eslint/parser": "^7.18.0",
86
+ "@typescript-eslint/eslint-plugin": "^7.18.0"
87
+ },
88
+ "repository": {
89
+ "type": "git",
90
+ "url": "git+https://github.com/Hashtagsmile/react-native-quick-preview.git"
91
+ },
92
+ "bugs": {
93
+ "url": "https://github.com/Hashtagsmile/react-native-quick-preview/issues"
94
+ },
95
+ "homepage": "https://github.com/Hashtagsmile/react-native-quick-preview#readme",
96
+ "publishConfig": {
97
+ "access": "public"
98
+ },
99
+ "engines": {
100
+ "node": ">=18"
101
+ },
102
+ "source": "src/index.ts"
103
+ }
@@ -0,0 +1,70 @@
1
+ // src/QuickPreviewAPI.ts
2
+ import type { QuickPreviewController, QuickPreviewOptions } from './types'
3
+ import type React from 'react'
4
+
5
+ // Persist across Fast Refresh / HMR
6
+ const STORE = Symbol.for('rnqp.controller')
7
+ type Store = { controller: QuickPreviewController | null }
8
+ const g = globalThis as Record<symbol, Store | undefined>
9
+ const store: Store = g[STORE] ?? (g[STORE] = { controller: null })
10
+
11
+ function warn() {
12
+ // __DEV__ is provided by RN/Metro; if TS complains, declare it in a globals.d.ts
13
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
14
+ // eslint-disable-next-line no-console
15
+ console.warn('[react-native-quick-preview] PreviewProvider not mounted yet.')
16
+ }
17
+ }
18
+
19
+ /**
20
+ * A static handle to the same controller as {@link useQuickPreview} — callable
21
+ * from anywhere, including outside React (services, store actions, event handlers).
22
+ * Before `<PreviewProvider>` mounts it is a safe no-op (with a dev warning).
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { QuickPreview } from 'react-native-quick-preview'
27
+ *
28
+ * QuickPreview.present(<Card />, { variant: 'sheet' })
29
+ * QuickPreview.update({ size: { maxHeight: 480 } })
30
+ * QuickPreview.close()
31
+ * ```
32
+ */
33
+ export const QuickPreview = {
34
+ /** @internal Registered by `<PreviewProvider />`. Not part of the public API. */
35
+ _set(c: QuickPreviewController | null) {
36
+ store.controller = c
37
+ },
38
+
39
+ /**
40
+ * Present `node` as the preview. Replaces the content if one is already open.
41
+ * @param node - the React element to show inside the preview.
42
+ * @param opts - presentation options ({@link QuickPreviewOptions}).
43
+ */
44
+ present(node: React.ReactNode, opts?: Partial<QuickPreviewOptions>) {
45
+ const c = store.controller
46
+ c ? c.present(node, opts) : warn()
47
+ },
48
+
49
+ /** Dismiss the current preview. No-op if nothing is open. */
50
+ close() {
51
+ const c = store.controller
52
+ c ? c.close() : warn()
53
+ },
54
+
55
+ /** Merge new options into the currently open preview. */
56
+ update(opts: Partial<QuickPreviewOptions>) {
57
+ const c = store.controller
58
+ c ? c.update(opts) : warn()
59
+ },
60
+
61
+ /** Whether a preview is currently open. */
62
+ isOpen() {
63
+ return !!store.controller?.isOpen()
64
+ },
65
+
66
+ /** @internal Dev/test convenience — don't use in app code. */
67
+ __unsafe_getController(): QuickPreviewController | null {
68
+ return store.controller
69
+ },
70
+ } as const
@@ -0,0 +1,115 @@
1
+ import React from 'react'
2
+ import type { StyleProp, ViewStyle } from 'react-native'
3
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler'
4
+ import Animated, {
5
+ runOnJS,
6
+ useAnimatedStyle,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from 'react-native-reanimated'
10
+ import { QuickPreview } from '../QuickPreviewAPI'
11
+ import type { QuickPreviewOptions } from '../types'
12
+
13
+ export type QuickPreviewPressableProps = {
14
+ children: React.ReactNode
15
+ /** Called for a normal tap (e.g., navigate) */
16
+ onPress?: () => void
17
+ /** Return the preview node to present */
18
+ renderPreview: () => React.ReactNode
19
+ /** Options forwarded to QuickPreview.present */
20
+ previewOptions?: Partial<QuickPreviewOptions>
21
+ /** Long-press delay (ms). Default 350 */
22
+ delay?: number
23
+ /** Press-down scale. Default 0.98 */
24
+ scale?: number
25
+ /**
26
+ * Called on the JS thread when the long press activates, right before the
27
+ * preview opens. Wire up haptics here, e.g.:
28
+ * onLongPressStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)}
29
+ * The library deliberately does not import expo-haptics itself: an optional
30
+ * require would break Metro bundling for apps that don't have it installed.
31
+ */
32
+ onLongPressStart?: () => void
33
+ /** Disable all interactions */
34
+ disabled?: boolean
35
+ /** Style applied to the animated wrapper (the thing that scales) */
36
+ style?: StyleProp<ViewStyle>
37
+ /** Accessibility */
38
+ accessible?: boolean
39
+ accessibilityLabel?: string
40
+ testID?: string
41
+ }
42
+
43
+ export function QuickPreviewPressable({
44
+ children,
45
+ onPress,
46
+ renderPreview,
47
+ previewOptions,
48
+ delay = 350,
49
+ scale = 0.98,
50
+ onLongPressStart,
51
+ disabled = false,
52
+ style,
53
+ accessible,
54
+ accessibilityLabel,
55
+ testID,
56
+ }: QuickPreviewPressableProps) {
57
+ const s = useSharedValue(1)
58
+
59
+ // Runs on the JS thread: renderPreview() creates React elements, which must
60
+ // never happen on the UI thread inside a worklet.
61
+ const openPreview = () => {
62
+ onLongPressStart?.()
63
+ QuickPreview.present(renderPreview(), {
64
+ variant: 'popover',
65
+ dismissOnBackdropPress: true,
66
+ ...previewOptions,
67
+ })
68
+ }
69
+
70
+ const longPress = Gesture.LongPress()
71
+ .enabled(!disabled)
72
+ .minDuration(delay)
73
+ .onTouchesDown(() => {
74
+ s.value = withTiming(scale, { duration: 90 })
75
+ })
76
+ .onFinalize(() => {
77
+ s.value = withTiming(1, { duration: 120 })
78
+ })
79
+ .onStart(() => {
80
+ runOnJS(openPreview)()
81
+ })
82
+
83
+ const tap = Gesture.Tap()
84
+ .enabled(!disabled)
85
+ .maxDuration(220)
86
+ .onTouchesDown(() => {
87
+ s.value = withTiming(scale, { duration: 90 })
88
+ })
89
+ .onFinalize(() => {
90
+ s.value = withTiming(1, { duration: 120 })
91
+ })
92
+ .onEnd(() => {
93
+ if (onPress) runOnJS(onPress)()
94
+ })
95
+
96
+ const gesture = Gesture.Exclusive(longPress, tap)
97
+
98
+ const animated = useAnimatedStyle(() => ({
99
+ transform: [{ scale: s.value }],
100
+ }))
101
+
102
+ return (
103
+ <GestureDetector gesture={gesture}>
104
+ <Animated.View
105
+ style={[animated, style]}
106
+ accessible={accessible}
107
+ accessibilityRole="button"
108
+ accessibilityLabel={accessibilityLabel}
109
+ testID={testID}
110
+ >
111
+ {children}
112
+ </Animated.View>
113
+ </GestureDetector>
114
+ )
115
+ }
@@ -0,0 +1,91 @@
1
+ import React from 'react'
2
+ import { BackHandler, AccessibilityInfo } from 'react-native'
3
+ import { Host, Portal } from 'react-native-portalize'
4
+ import { QuickPreviewOptions, QuickPreviewController } from './types'
5
+ import { QuickPreviewRoot } from './internal/QuickPreviewRoot'
6
+ import { QuickPreview as QuickPreviewStatic } from './QuickPreviewAPI'
7
+
8
+ export const QuickPreviewContext = React.createContext<QuickPreviewController | null>(null)
9
+
10
+ /**
11
+ * Hosts the preview layer and registers the controller. Mount this once, near the
12
+ * root of your app, inside a `<GestureHandlerRootView>`. After it's mounted you can
13
+ * present previews via {@link useQuickPreview} or the static `QuickPreview` handle.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <GestureHandlerRootView style={{ flex: 1 }}>
18
+ * <PreviewProvider>
19
+ * <YourApp />
20
+ * </PreviewProvider>
21
+ * </GestureHandlerRootView>
22
+ * ```
23
+ */
24
+ export function PreviewProvider({ children }: { children: React.ReactNode }) {
25
+ const [content, setContent] = React.useState<React.ReactNode | null>(null)
26
+ const [options, setOptions] = React.useState<QuickPreviewOptions | undefined>(undefined)
27
+ const openRef = React.useRef(false)
28
+
29
+ const announce = React.useCallback((msg: string) => {
30
+ // eslint-disable-next-line no-empty
31
+ try { AccessibilityInfo.announceForAccessibility?.(msg) } catch {}
32
+ }, [])
33
+
34
+ const close = React.useCallback(() => {
35
+ if (!openRef.current) return
36
+ openRef.current = false
37
+ setContent(null)
38
+ setOptions(undefined)
39
+ announce('Quick preview closed')
40
+ }, [announce])
41
+
42
+ const present = React.useCallback(
43
+ (node: React.ReactNode, opts?: Partial<QuickPreviewOptions>) => {
44
+ // Replace, don't merge: options from a previous presentation must not
45
+ // leak into the next one. Merging is what update() is for.
46
+ setOptions(opts)
47
+ openRef.current = true
48
+ setContent(() => node)
49
+ announce('Quick preview opened')
50
+ },
51
+ [announce]
52
+ )
53
+
54
+ const update = React.useCallback((opts: Partial<QuickPreviewOptions>) => {
55
+ setOptions(prev => ({ ...prev, ...opts }))
56
+ }, [])
57
+
58
+ React.useEffect(() => {
59
+ const sub = BackHandler.addEventListener('hardwareBackPress', () => {
60
+ if (openRef.current) { close(); return true }
61
+ return false
62
+ })
63
+ return () => sub.remove()
64
+ }, [close])
65
+
66
+ const value = React.useMemo<QuickPreviewController>(
67
+ () => ({ present, close, update, isOpen: () => openRef.current }),
68
+ [present, close, update]
69
+ )
70
+
71
+ // Register the static API (layout effect = available ASAP)
72
+ React.useLayoutEffect(() => {
73
+ QuickPreviewStatic._set(value)
74
+ return () => QuickPreviewStatic._set(null)
75
+ }, [value])
76
+
77
+ return (
78
+ <QuickPreviewContext.Provider value={value}>
79
+ <Host>
80
+ {children}
81
+ {content ? (
82
+ <Portal>
83
+ <QuickPreviewRoot options={options} onRequestClose={close}>
84
+ {content}
85
+ </QuickPreviewRoot>
86
+ </Portal>
87
+ ) : null}
88
+ </Host>
89
+ </QuickPreviewContext.Provider>
90
+ )
91
+ }
@@ -0,0 +1,56 @@
1
+ import React from 'react'
2
+ import { Portal } from 'react-native-portalize'
3
+ import { QuickPreviewOptions } from '../types'
4
+ import { QuickPreviewRoot } from '../internal/QuickPreviewRoot'
5
+
6
+ /**
7
+ * Props for the controlled, headless `QuickPreviewComponent`. Extends
8
+ * {@link QuickPreviewOptions} (so `variant`, `size`, `dismissOn*`, and the
9
+ * lifecycle callbacks are all accepted).
10
+ */
11
+ export type HeadlessQuickPreviewProps = {
12
+ /** Whether the preview is shown. You own this state. */
13
+ visible: boolean
14
+ /** Called when the preview requests to close (backdrop press, swipe, back button). */
15
+ onClose: () => void
16
+ /**
17
+ * Render into a portal above the app. Disable only if you're providing your
18
+ * own host/overlay.
19
+ * @defaultValue `true`
20
+ */
21
+ portal?: boolean
22
+ } & Partial<QuickPreviewOptions> & {
23
+ /** The preview content. */
24
+ children: React.ReactNode
25
+ }
26
+
27
+ /**
28
+ * A controlled, headless preview component — you own the `visible` state. Useful
29
+ * when you prefer a declarative component over the imperative
30
+ * {@link useQuickPreview} hook or the static `QuickPreview` handle.
31
+ *
32
+ * Exported as `QuickPreviewComponent`.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * const [visible, setVisible] = useState(false)
37
+ * <QuickPreviewComponent visible={visible} onClose={() => setVisible(false)} variant="sheet">
38
+ * <Card />
39
+ * </QuickPreviewComponent>
40
+ * ```
41
+ */
42
+ export function QuickPreview({
43
+ visible,
44
+ onClose,
45
+ portal = true,
46
+ children,
47
+ ...opts
48
+ }: HeadlessQuickPreviewProps) {
49
+ if (!visible) return null
50
+ const node = (
51
+ <QuickPreviewRoot options={opts} onRequestClose={onClose}>
52
+ {children}
53
+ </QuickPreviewRoot>
54
+ )
55
+ return portal ? <Portal>{node}</Portal> : node
56
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,24 @@
1
- export { QuickLook } from './QuickLook';
2
- export type { QuickLookProps, ThemeMode, CloseReason } from './QuickLookProperties';
1
+ // src/index.ts
2
+
3
+ // core
4
+ export { PreviewProvider } from './context'
5
+ export { useQuickPreview } from './useQuickPreview'
6
+
7
+ // static API (Gorhom-style)
8
+ export { QuickPreview } from './QuickPreviewAPI'
9
+
10
+ // headless component exported under a different name
11
+ export { QuickPreview as QuickPreviewComponent } from './headless/QuickPreview'
12
+
13
+ // types / helpers
14
+ export type {
15
+ QuickPreviewOptions,
16
+ QuickPreviewVariant,
17
+ QuickPreviewSize,
18
+ QuickPreviewController,
19
+ } from './types'
20
+ export { QuickPreviewScrollView } from './internal/components/QuickPreviewScrollView'
21
+
22
+
23
+ // Addons
24
+ export { QuickPreviewPressable } from './addons/QuickPreviewPressable'
@@ -0,0 +1,73 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
4
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
5
+ import { QuickPreviewOptions } from '../types'
6
+ import { Backdrop } from './components/Backdrop'
7
+ import { SheetContainer } from './components/SheetContainer'
8
+ import { PopoverContainer } from './components/PopoverContainer'
9
+
10
+ export function QuickPreviewRoot({
11
+ options,
12
+ onRequestClose,
13
+ children,
14
+ }: {
15
+ options?: QuickPreviewOptions
16
+ onRequestClose: () => void
17
+ children: React.ReactNode
18
+ }) {
19
+ const { top, bottom } = useSafeAreaInsets()
20
+ const variant = options?.variant ?? 'popover'
21
+ const dismissOnBackdropPress = options?.dismissOnBackdropPress ?? true
22
+
23
+ const opacity = useSharedValue(0)
24
+
25
+ const cbs = React.useRef({
26
+ onOpenStart: options?.onOpenStart,
27
+ onOpenEnd: options?.onOpenEnd,
28
+ onCloseStart: options?.onCloseStart,
29
+ onCloseEnd: options?.onCloseEnd,
30
+ })
31
+ React.useEffect(() => {
32
+ cbs.current = {
33
+ onOpenStart: options?.onOpenStart,
34
+ onOpenEnd: options?.onOpenEnd,
35
+ onCloseStart: options?.onCloseStart,
36
+ onCloseEnd: options?.onCloseEnd,
37
+ }
38
+ }, [options?.onOpenStart, options?.onOpenEnd, options?.onCloseStart, options?.onCloseEnd])
39
+
40
+ React.useEffect(() => {
41
+ cbs.current.onOpenStart?.()
42
+ opacity.value = withTiming(1, { duration: 180 }, finished => {
43
+ if (finished) cbs.current.onOpenEnd?.()
44
+ })
45
+ return () => {
46
+ cbs.current.onCloseStart?.()
47
+ cbs.current.onCloseEnd?.()
48
+ }
49
+ // run once (mount-only effect)
50
+ }, [])
51
+
52
+ const containerStyle = useAnimatedStyle(() => ({ opacity: opacity.value }))
53
+
54
+ return (
55
+ <View style={StyleSheet.absoluteFill} pointerEvents="box-none" accessibilityViewIsModal>
56
+ <Animated.View style={[StyleSheet.absoluteFill, containerStyle]} pointerEvents="box-none">
57
+ <Backdrop
58
+ onPress={dismissOnBackdropPress ? onRequestClose : undefined}
59
+ dismissOnBackdropPress={dismissOnBackdropPress}
60
+ />
61
+ {variant === 'sheet' ? (
62
+ <SheetContainer insets={{ top, bottom }} options={options} onRequestClose={onRequestClose}>
63
+ {children}
64
+ </SheetContainer>
65
+ ) : (
66
+ <PopoverContainer insets={{ top, bottom }} options={options} onRequestClose={onRequestClose}>
67
+ {children}
68
+ </PopoverContainer>
69
+ )}
70
+ </Animated.View>
71
+ </View>
72
+ )
73
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import { Pressable, StyleSheet } from 'react-native'
3
+ import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'
4
+
5
+ export function Backdrop({
6
+ onPress,
7
+ dismissOnBackdropPress = true,
8
+ }: {
9
+ onPress?: () => void
10
+ dismissOnBackdropPress?: boolean
11
+ }) {
12
+ const opacity = useSharedValue(0)
13
+ React.useEffect(() => {
14
+ opacity.value = withTiming(1, { duration: 160 })
15
+ return () => { opacity.value = 0 }
16
+ }, [opacity])
17
+
18
+ const style = useAnimatedStyle(() => ({
19
+ backgroundColor: 'rgba(0,0,0,0.5)',
20
+ ...StyleSheet.absoluteFillObject,
21
+ opacity: opacity.value,
22
+ }))
23
+
24
+ return (
25
+ <Animated.View style={style} pointerEvents={dismissOnBackdropPress && onPress ? 'auto' : 'none'}>
26
+ {dismissOnBackdropPress && onPress ? (
27
+ <Pressable
28
+ style={StyleSheet.absoluteFill}
29
+ onPress={onPress}
30
+ accessibilityRole="button"
31
+ accessibilityLabel="Close preview"
32
+ />
33
+ ) : null}
34
+ </Animated.View>
35
+ )
36
+ }