react-native-quick-preview 1.0.5 β†’ 2.0.1

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Oliver Lindblad
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Oliver Lindblad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,188 +1,292 @@
1
- # react-native-quick-preview
2
-
3
- A beautiful, customizable quick preview modal component for React Native.
4
- Think **Gorhom Bottom Sheet**, but for quick previews.
5
-
6
- Perfect for:
7
- - Instagram-style **long-press previews**
8
- - πŸ›οΈ **E-commerce product quick views**
9
- - πŸ“° **Article teasers**
10
- - ✈️ **Travel destination peeks**
11
- - …any content that needs a **quick peek before full navigation**
12
-
13
- ---
14
-
15
- ## ✨ Features
16
-
17
- - 🎨 Smooth **fade, scale, and swipe-to-close animations**
18
- - 🎯 **Universal content** – works with any layout (products, posts, etc.)
19
- - πŸŽ›οΈ **Customizable** – theme, behavior, styling, backdrop
20
- - β™Ώ **Accessibility ready** – screen reader & keyboard navigation
21
- - πŸ“± **Cross-platform** – iOS & Android
22
- - πŸ”§ **TypeScript support**
23
- - ⚑ **Performance optimized** – native drivers
24
-
25
- ---
26
-
27
- ## πŸ“¦ Installation
28
-
29
- ```bash
30
- npm install react-native-quick-preview
31
- # or
32
- yarn add react-native-quick-preview
33
- ```
34
-
35
- ---
36
-
37
- ## πŸš€ Quick Start
38
-
39
- ```tsx
40
- import React, { useState } from 'react';
41
- import { View, Text, TouchableOpacity } from 'react-native';
42
- import { QuickPreview } from 'react-native-quick-preview';
43
-
44
- export default function App() {
45
- const [visible, setVisible] = useState(false);
46
-
47
- return (
48
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
49
- <TouchableOpacity onPress={() => setVisible(true)}>
50
- <Text>Show Quick Look</Text>
51
- </TouchableOpacity>
52
-
53
- <QuickPreview visible={visible} onClose={() => setVisible(false)}>
54
- <View style={{ backgroundColor: '#fff', padding: 20 }}>
55
- <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Quick Preview Content</Text>
56
- <Text style={{ marginTop: 10 }}>Any custom content goes here.</Text>
57
- </View>
58
- </QuickPreview>
59
- </View>
60
- );
61
- }
62
- ```
63
-
64
- ---
65
-
66
- ## πŸ“– Usage Examples
67
-
68
- ### πŸ›οΈ E-commerce Product Quick View
69
- ```tsx
70
- <QuickPreview visible={visible} onClose={onClose} onPressCard={onViewDetails}>
71
- <View>
72
- <Image source={{ uri: product.image }} style={{ width: '100%', height: 200 }} />
73
- <Text>{product.name}</Text>
74
- <Text>{product.price}</Text>
75
- </View>
76
- </QuickPreview>
77
- ```
78
-
79
- ### πŸ“° Article Preview
80
- ```tsx
81
- <QuickPreview visible={visible} onClose={onClose} theme="dark">
82
- <View style={{ backgroundColor: '#1a1a1a' }}>
83
- <Image source={{ uri: article.coverImage }} style={{ width: '100%', height: 200 }} />
84
- <Text style={{ color: '#fff' }}>{article.title}</Text>
85
- <Text style={{ color: '#ccc' }}>{article.excerpt}</Text>
86
- </View>
87
- </QuickPreview>
88
- ```
89
-
90
- ### ✈️ Travel Destination Peek
91
- ```tsx
92
- <QuickPreview visible={visible} onClose={onClose}>
93
- <Image source={{ uri: destination.image }} style={{ width: '100%', height: 200 }} />
94
- <Text>{destination.title}</Text>
95
- <Text>From ${destination.price}</Text>
96
- </QuickPreview>
97
- ```
98
-
99
- ---
100
-
101
- ## πŸŽ›οΈ API Reference
102
-
103
- ### Props
104
-
105
- | Prop | Type | Default | Description |
106
- |------|------|---------|-------------|
107
- | `visible` | `boolean` | **required** | Controls modal visibility |
108
- | `onClose` | `() => void` | **required** | Called when modal closes |
109
- | `onPressCard` | `() => void` | `undefined` | Card press handler |
110
- | `children` | `React.ReactNode` | `undefined` | Content to render |
111
- | `theme` | `'light' \| 'dark'` | `'light'` | Overlay theme |
112
- | `backdropOpacity` | `number` | `0.5 / 0.8` | Overlay opacity |
113
- | `animationDuration` | `number` | `220` | Animation duration |
114
- | `closeOnBackdropPress` | `boolean` | `true` | Close on backdrop press |
115
- | `closeOnBackButton` | `boolean` | `true` | Close on Android back button |
116
- | `enableSwipeToClose` | `boolean` | `true` | Swipe down to close |
117
- | `swipeThreshold` | `number` | `80` | Swipe distance threshold |
118
- | `unmountOnExit` | `boolean` | `true` | Unmount when hidden |
119
- | `avoidKeyboard` | `boolean` | `false` | Avoid keyboard overlap |
120
- | `renderBackdrop` | `(opacity) => ReactNode` | `undefined` | Custom backdrop |
121
- | `onBackdropPress` | `() => void` | `undefined` | Backdrop press handler |
122
- | `testID` | `string` | `'quickpreview'` | Test identifier |
123
- | `accessibilityLabel` | `string` | `'Quick look'` | A11y label |
124
- | `stylesOverride` | `object` | `{}` | Override default styles |
125
-
126
- ---
127
-
128
- ## 🎨 Customization
129
-
130
- ### Custom Backdrop
131
- ```tsx
132
- <QuickPreview
133
- visible={visible}
134
- onClose={onClose}
135
- renderBackdrop={(opacity) => (
136
- <Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: 'rgba(255,0,0,0.5)', opacity }]} />
137
- )}
138
- >
139
- {/* content */}
140
- </QuickPreview>
141
- ```
142
-
143
- ### Custom Styles
144
- ```tsx
145
- <QuickPreview
146
- visible={visible}
147
- onClose={onClose}
148
- stylesOverride={{
149
- container: { borderRadius: 24, backgroundColor: '#000' },
150
- overlay: { backgroundColor: 'rgba(0,0,0,0.9)' }
151
- }}
152
- >
153
- {/* content */}
154
- </QuickPreview>
155
- ```
156
-
157
- ---
158
-
159
- ## πŸ§ͺ Testing
160
-
161
- ```tsx
162
- import { render, fireEvent } from '@testing-library/react-native';
163
-
164
- test('QuickPreview closes on backdrop press', () => {
165
- const onClose = jest.fn();
166
- const { getByTestId } = render(
167
- <QuickPreview visible={true} onClose={onClose} testID="quickpreview">
168
- <Text>Test content</Text>
169
- </QuickPreview>
170
- );
171
-
172
- fireEvent.press(getByTestId('ql-backdrop'));
173
- expect(onClose).toHaveBeenCalled();
174
- });
175
- ```
176
-
177
- ---
178
-
179
- ## πŸ“± Platform Support
180
- - βœ… iOS 12+
181
- - βœ… Android API 21+
182
- - βœ… Expo SDK 48+
183
-
184
- ---
185
-
186
- ## πŸ“„ License
187
-
188
- MIT Β© Oliver Lindblad
1
+ # react-native-quick-preview
2
+
3
+ [![npm](https://img.shields.io/npm/v/react-native-quick-preview.svg)](https://www.npmjs.com/package/react-native-quick-preview)
4
+ [![CI](https://github.com/Hashtagsmile/react-native-quick-preview/actions/workflows/ci.yml/badge.svg)](https://github.com/Hashtagsmile/react-native-quick-preview/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/Hashtagsmile/react-native-quick-preview/blob/main/LICENSE)
6
+
7
+ Long-press an item to peek at its content, swipe down to dismiss. The kind of preview you know from Instagram or iOS Peek & Pop, as a small headless library for React Native.
8
+
9
+ I wanted this pattern in my own apps and couldn't find a maintained library for it, so I built one. The API follows the approach popularized by [Gorhom's bottom sheet](https://github.com/gorhom/react-native-bottom-sheet): mount a provider once, then present from anywhere.
10
+
11
+ <p align="center">
12
+ <img src="https://raw.githubusercontent.com/Hashtagsmile/react-native-quick-preview/main/demo-feed.gif" width="46%" alt="Social feed β€” long-press a photo to peek the post, tap to open" />
13
+ &nbsp;
14
+ <img src="https://raw.githubusercontent.com/Hashtagsmile/react-native-quick-preview/main/demo-shop.gif" width="46%" alt="Shop β€” long-press a product for a bottom-sheet quick-view" />
15
+ </p>
16
+
17
+ What you get:
18
+
19
+ - Headless: you render the preview content, the library handles presentation, gestures and dismissal
20
+ - Two variants: centered `popover` and bottom `sheet`
21
+ - Present from a hook, or from a static handle that also works outside React components
22
+ - Swipe-to-dismiss, backdrop press, Android back button handling
23
+ - Screen reader announcements and accessibility labels
24
+ - TypeScript types, CJS + ESM builds, zero dependencies of its own
25
+
26
+ ## Why use this
27
+
28
+ The long-press peek is a common interaction, but the existing options each stop short in a specific way:
29
+
30
+ - **Expo Router's `Link.Preview`** is the native peek-and-pop β€” but it's **iOS-only**, it previews the **route a link points to** (it navigates on tap), and it requires Expo Router with a native stack.
31
+ - **Native context-menu libraries** (`react-native-ios-context-menu`, `zeego`) render a real custom peek β€” but only on **iOS**, because the "preview floating above a menu" is an iOS-only OS affordance (`UITargetedPreview`) with no Android equivalent. They also ship native modules, so they don't run in Expo Go.
32
+ - **`@gorhom/bottom-sheet`** is the go-to sheet, but it's sheet-only (no peek), and its imperative API still needs a mounted component plus a ref β€” there's no static handle you can call from outside React.
33
+
34
+ This library fills the intersection none of them cover:
35
+
36
+ > **A long-press peek that renders any React node identically on iOS *and* Android, with no native module β€” presentable from anywhere, no router required.**
37
+
38
+ Concretely, reach for it when you want:
39
+
40
+ - **The same peek on both platforms.** Pure JS + Reanimated renders your content the same way on iOS and Android.
41
+ - **To preview arbitrary content, not a route or a menu.** A product card, an image, a profile β€” anything, without navigating.
42
+ - **No native build.** Works in Expo Go and bare React Native; no config plugin, no prebuild.
43
+ - **To present from anywhere.** `QuickPreview.present(node)` is importable and callable from a service, a store action, or any non-React code (after `PreviewProvider` is mounted once at the root).
44
+
45
+ **When *not* to use it:** if you're iOS-only and want pixel-perfect native peek fidelity and system haptics, use `react-native-ios-context-menu` or Expo Router's `Link.Preview` β€” a Reanimated re-creation approximates the native feel, it doesn't reproduce it. And if you just need a bottom sheet, use `@gorhom/bottom-sheet`. This library is for the cross-platform, arbitrary-content peek specifically.
46
+
47
+ ## Add it to your app
48
+
49
+ **How it works.** Wrap each list or grid item in `QuickPreviewPressable`. A **tap** runs `onPress` (go straight to your full screen); a **long-press** opens the peek returned by `renderPreview`. Make that peek tappable and tapping it opens the full screen too β€” otherwise it just displays until dismissed (swipe down, tap outside, or Android back).
50
+
51
+ ```text
52
+ β”Œβ”€ tap ─────────────────────────────► your detail screen
53
+ item ───
54
+ └─ long-press ─► peek ─┬─ tap peek ─► your detail screen
55
+ └─ swipe / tap outside ─► back to the list
56
+ ```
57
+
58
+ You build **one new thing per item: the preview content** β€” usually a compact version of a card you already have. The "full screen" is just the route you already navigate to, so it isn't extra work.
59
+
60
+ ### 1 Β· Install
61
+
62
+ ```bash
63
+ npm install react-native-quick-preview
64
+ ```
65
+
66
+ It has four peer dependencies. **Most apps already have the first three** β€” React Navigation and Expo Router both pull them in β€” so install whatever's missing:
67
+
68
+ ```bash
69
+ npx expo install react-native-reanimated react-native-gesture-handler react-native-safe-area-context
70
+ npm install react-native-portalize
71
+ ```
72
+
73
+ | Peer dependency | What it's for | Native? |
74
+ | --- | --- | --- |
75
+ | `react-native-reanimated` | The peek animation (worklets) | yes |
76
+ | `react-native-gesture-handler` | Long-press & swipe gestures | yes |
77
+ | `react-native-safe-area-context` | Sheet insets | yes |
78
+ | `react-native-portalize` | Renders the peek above your app | no β€” pure JS |
79
+
80
+ If Reanimated and Gesture Handler are already installed, adding this library is **JS-only β€” no native rebuild**. (Reanimated needs its Babel plugin; Expo sets this up via `babel-preset-expo`. Versions and bare-RN notes are under [Requirements](#requirements).)
81
+
82
+ ### 2 Β· Wrap your app once
83
+
84
+ ```tsx
85
+ import { GestureHandlerRootView } from 'react-native-gesture-handler'
86
+ import { PreviewProvider } from 'react-native-quick-preview'
87
+
88
+ export default function App() {
89
+ return (
90
+ <GestureHandlerRootView style={{ flex: 1 }}>
91
+ <PreviewProvider>
92
+ <YourApp />
93
+ </PreviewProvider>
94
+ </GestureHandlerRootView>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ### 3 Β· Drop it onto a list item
100
+
101
+ ```tsx
102
+ // note: touchables used *inside* a preview must come from gesture-handler, not react-native
103
+ import { Pressable } from 'react-native-gesture-handler'
104
+ import { QuickPreview, QuickPreviewPressable } from 'react-native-quick-preview'
105
+
106
+ <QuickPreviewPressable
107
+ onPress={() => navigation.navigate('Product', { id: product.id })} // tap β†’ full screen
108
+ renderPreview={() => ( // long-press β†’ peek
109
+ <Pressable
110
+ onPress={() => {
111
+ QuickPreview.close()
112
+ navigation.navigate('Product', { id: product.id }) // tap the peek β†’ full screen
113
+ }}
114
+ >
115
+ <ProductCard product={product} /> {/* reuse a card you already have */}
116
+ </Pressable>
117
+ )}
118
+ >
119
+ <Thumbnail source={product.image} />
120
+ </QuickPreviewPressable>
121
+ ```
122
+
123
+ That's the whole integration β€” three files touched, reusing your existing detail route as the destination.
124
+
125
+ > Prefer to trigger it yourself? Grab the hook β€” `const { present } = useQuickPreview()` β€” and call `present(node, options)` from any handler, or use the static `QuickPreview.present(node, options)` from outside React (below).
126
+
127
+ ## Requirements
128
+
129
+ - **React Native** β‰₯ 0.64 (peer). Tested on RN 0.81.
130
+ - **React** β‰₯ 18.2.
131
+ - **Peer dependencies** (must be installed in your app): `react-native-reanimated` (β‰₯ 3; both Reanimated 3 and 4 are supported), `react-native-gesture-handler` (β‰₯ 2), `react-native-safe-area-context` (β‰₯ 4), `react-native-portalize`.
132
+ - **Reanimated's Babel plugin** must be configured β€” this is required, not optional. React Native consumes this library's TypeScript source (the standard distribution model for Reanimated libraries), so the plugin transforms its worklets as part of your app's build. Expo configures it for you via `babel-preset-expo`; bare RN apps add it manually (`react-native-worklets/plugin` on Reanimated 4, `react-native-reanimated/plugin` on Reanimated 3). On Expo SDK 54, do **not** also add the plugin by hand β€” `babel-preset-expo` already includes it, and a duplicate breaks worklets.
133
+ - Your app must render `<PreviewProvider>` once, inside a `<GestureHandlerRootView>` (step 2 above).
134
+ - `expo-haptics` is **optional** β€” only needed if you want haptics; pass it to `QuickPreviewPressable` via `onLongPressStart`.
135
+
136
+ ## Presenting from outside components
137
+
138
+ `QuickPreview` is a static handle to the same controller. Use it in event handlers, services, or anywhere React context isn't available. Before the provider mounts it's a safe no-op (with a warning in dev builds).
139
+
140
+ ```tsx
141
+ import { QuickPreview } from 'react-native-quick-preview'
142
+
143
+ QuickPreview.present(<MyPreviewContent />, { variant: 'sheet' })
144
+ QuickPreview.update({ size: { maxHeight: 480 } })
145
+ QuickPreview.isOpen() // boolean
146
+ QuickPreview.close()
147
+ ```
148
+
149
+ ## QuickPreviewPressable
150
+
151
+ Most of the time you want the same thing: long-press opens a preview, a normal tap navigates. `QuickPreviewPressable` wires that up, including a press-down scale animation:
152
+
153
+ ```tsx
154
+ import * as Haptics from 'expo-haptics' // optional
155
+ import { QuickPreviewPressable } from 'react-native-quick-preview'
156
+
157
+ <QuickPreviewPressable
158
+ onPress={() => router.push(`/product/${product.id}`)}
159
+ renderPreview={() => <ProductPreview product={product} />}
160
+ previewOptions={{ variant: 'popover' }}
161
+ delay={350} // long-press delay in ms
162
+ onLongPressStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)}
163
+ >
164
+ <ProductCard product={product} />
165
+ </QuickPreviewPressable>
166
+ ```
167
+
168
+ `onLongPressStart` fires on the JS thread right before the preview opens. Use it for haptics or analytics. The library doesn't import `expo-haptics` itself, so you stay in control of the dependency (and Metro never has to resolve a package you don't use).
169
+
170
+ ## Tappable preview content
171
+
172
+ The preview renders inside a gesture-handler overlay. Buttons and tappable areas **inside your preview content** should use touchables from `react-native-gesture-handler` (not `react-native`) β€” React Native's own `Pressable` / `TouchableOpacity` don't reliably receive taps inside that overlay.
173
+
174
+ ```tsx
175
+ // βœ— tap may not fire inside the preview
176
+ import { Pressable } from 'react-native'
177
+
178
+ // βœ“ works inside the preview
179
+ import { Pressable } from 'react-native-gesture-handler'
180
+
181
+ present(
182
+ <Pressable onPress={() => { QuickPreview.close(); router.push('/detail') }}>
183
+ <ProductPreview product={product} />
184
+ </Pressable>,
185
+ { variant: 'popover' }
186
+ )
187
+ ```
188
+
189
+ ## Scrollable previews
190
+
191
+ If the preview content scrolls, use `QuickPreviewScrollView` instead of a plain `ScrollView`. Otherwise the scroll gesture and the swipe-to-dismiss gesture fight each other.
192
+
193
+ ```tsx
194
+ import { QuickPreviewScrollView } from 'react-native-quick-preview'
195
+
196
+ present(
197
+ <QuickPreviewScrollView>
198
+ <LongArticleContent />
199
+ </QuickPreviewScrollView>,
200
+ { variant: 'sheet' }
201
+ )
202
+ ```
203
+
204
+ ## API reference
205
+
206
+ ### `present(node, options?)`
207
+
208
+ Available from `useQuickPreview()` and the static `QuickPreview`. Presents `node` as the preview content. Calling it while a preview is open replaces the content.
209
+
210
+ | Option | Type | Default | Description |
211
+ | --- | --- | --- | --- |
212
+ | `variant` | `'popover' \| 'sheet'` | `'popover'` | Centered card or bottom sheet |
213
+ | `size` | `'auto' \| number \| { maxHeight?, maxWidth? }` | `'auto'` | Constrains the preview container |
214
+ | `dismissOnBackdropPress` | `boolean` | `true` | Close when the backdrop is tapped |
215
+ | `dismissOnPanDown` | `boolean` | `true` | Close on swipe-down gesture |
216
+ | `accessibilityLabel` | `string` | | Label announced for the preview container |
217
+ | `onOpenStart` / `onOpenEnd` | `() => void` | | Open animation lifecycle |
218
+ | `onCloseStart` / `onCloseEnd` | `() => void` | | Close animation lifecycle |
219
+
220
+ ### Controller
221
+
222
+ Both `useQuickPreview()` and the static `QuickPreview` expose:
223
+
224
+ | Method | Description |
225
+ | --- | --- |
226
+ | `present(node, options?)` | Open a preview with the given content |
227
+ | `close()` | Dismiss the current preview |
228
+ | `update(options)` | Merge new options into the open preview |
229
+ | `isOpen()` | Whether a preview is currently open |
230
+
231
+ ### Exports
232
+
233
+ | Export | Purpose |
234
+ | --- | --- |
235
+ | `PreviewProvider` | Hosts the preview layer; mount once at the root |
236
+ | `useQuickPreview` | Controller from React context (throws outside the provider) |
237
+ | `QuickPreview` | Static controller handle (safe no-op before mount) |
238
+ | `QuickPreviewPressable` | Long-press-to-preview wrapper with tap-through |
239
+ | `QuickPreviewScrollView` | Gesture-aware ScrollView for preview content |
240
+ | `QuickPreviewComponent` | Low-level headless component, for fully custom hosts |
241
+ | `QuickPreviewOptions`, `QuickPreviewVariant`, `QuickPreviewSize`, `QuickPreviewController` | Public TypeScript types |
242
+
243
+ ## Accessibility
244
+
245
+ Open and close are announced to screen readers via `AccessibilityInfo.announceForAccessibility`, the Android hardware back button closes an open preview, the backdrop is an accessible close target, and `accessibilityLabel` on the container is forwarded to assistive tech.
246
+
247
+ ## Example app
248
+
249
+ The [repo](https://github.com/Hashtagsmile/react-native-quick-preview) contains an Expo example app with e-commerce, article and travel preview patterns:
250
+
251
+ ```bash
252
+ git clone https://github.com/Hashtagsmile/react-native-quick-preview.git
253
+ cd react-native-quick-preview
254
+ npm install
255
+ npm run build
256
+ npm run example
257
+ ```
258
+
259
+ The example app is on **Expo SDK 54** (React Native 0.81, Reanimated 4) and runs in Expo Go on iOS and Android. See its [recording guide](https://github.com/Hashtagsmile/react-native-quick-preview/blob/main/apps/expo-quick-preview-example/RECORDING.md) if you want to reproduce the demo GIFs.
260
+
261
+ ## Compatibility
262
+
263
+ | | Status |
264
+ | --- | --- |
265
+ | **Platforms** | iOS and Android (identical rendering β€” it's pure JS/Reanimated) |
266
+ | **Expo** | Works in Expo Go and dev clients. No config plugin, no prebuild, no native module. |
267
+ | **Bare React Native** | Supported (configure the Reanimated Babel plugin yourself). |
268
+ | **New Architecture** | Supported. Verified on RN 0.81 + Reanimated 4 (New-Arch-only). |
269
+ | **Old Architecture** | Supported via Reanimated 3. |
270
+ | **Web (react-native-web)** | Not a target. It may partially work, but gestures/portals aren't tested there. |
271
+
272
+ ## Limitations
273
+
274
+ Be clear-eyed about what this is and isn't:
275
+
276
+ - **It's a re-creation, not the native OS peek.** On iOS, `react-native-ios-context-menu` and Expo Router's `Link.Preview` use the system `UIContextMenuInteraction` (system blur, morph, tuned haptics). This library approximates that feel with Reanimated β€” it does not reproduce it pixel-for-pixel. If you're iOS-only and want native fidelity, use one of those instead (see [Why use this](#why-use-this)).
277
+ - **No built-in action menu.** This presents a *preview*, not a context menu with actions. Render your own buttons inside the preview if you need them.
278
+ - **Haptics aren't automatic.** Native context menus fire system haptics for free; here you wire them via `onLongPressStart` (one line β€” see `QuickPreviewPressable`).
279
+ - **One preview at a time.** Calling `present()` while a preview is open replaces the content; previews don't stack.
280
+ - **Light theme by default.** The popover/sheet containers use a white background. Style your own content freely; a dark container background isn't a built-in option yet.
281
+ - **Screen-reader announcements are English** ("Quick preview opened/closed"). Labels you pass are used verbatim, but the built-in announcement strings aren't localized yet.
282
+ - **Requires the peer setup.** No `GestureHandlerRootView` + `PreviewProvider`, or a missing Reanimated Babel plugin, and it won't work β€” this is not a drop-in with zero configuration.
283
+
284
+ ## Versioning
285
+
286
+ This package follows semver. Release notes live in the [CHANGELOG](https://github.com/Hashtagsmile/react-native-quick-preview/blob/main/CHANGELOG.md).
287
+
288
+ Heads up if you're coming from 1.x: version 2.0.0 replaced the old `<QuickPreview visible onClose>` component with the provider + controller architecture described above. v1 code will not compile against v2. The changelog has migration notes.
289
+
290
+ ## License
291
+
292
+ MIT Β© Oliver Lindblad