react-native-quick-preview 1.0.5 β 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/LICENSE +21 -21
- package/README.md +288 -188
- package/dist/index.d.mts +244 -35
- package/dist/index.d.ts +244 -35
- package/dist/index.js +419 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +407 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +103 -98
- package/src/QuickPreviewAPI.ts +70 -0
- package/src/addons/QuickPreviewPressable.tsx +115 -0
- package/src/context.tsx +91 -0
- package/src/headless/QuickPreview.tsx +56 -0
- package/src/index.ts +24 -2
- package/src/internal/QuickPreviewRoot.tsx +73 -0
- package/src/internal/components/Backdrop.tsx +36 -0
- package/src/internal/components/PopoverContainer.tsx +58 -0
- package/src/internal/components/QuickPreviewScrollView.tsx +21 -0
- package/src/internal/components/SheetContainer.tsx +84 -0
- package/src/internal/resolveSize.ts +17 -0
- package/src/types.ts +93 -0
- package/src/useQuickPreview.ts +28 -0
- package/src/QuickPreview.tsx +0 -222
- package/src/QuickPreviewProperties.ts +0 -46
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,288 @@
|
|
|
1
|
-
# react-native-quick-preview
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Instagram
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```tsx
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
1
|
+
# react-native-quick-preview
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-quick-preview)
|
|
4
|
+
[](https://github.com/Hashtagsmile/react-native-quick-preview/actions/workflows/ci.yml)
|
|
5
|
+
[](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
|
+

|
|
12
|
+
|
|
13
|
+
What you get:
|
|
14
|
+
|
|
15
|
+
- Headless: you render the preview content, the library handles presentation, gestures and dismissal
|
|
16
|
+
- Two variants: centered `popover` and bottom `sheet`
|
|
17
|
+
- Present from a hook, or from a static handle that also works outside React components
|
|
18
|
+
- Swipe-to-dismiss, backdrop press, Android back button handling
|
|
19
|
+
- Screen reader announcements and accessibility labels
|
|
20
|
+
- TypeScript types, CJS + ESM builds, zero dependencies of its own
|
|
21
|
+
|
|
22
|
+
## Why use this
|
|
23
|
+
|
|
24
|
+
The long-press peek is a common interaction, but the existing options each stop short in a specific way:
|
|
25
|
+
|
|
26
|
+
- **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.
|
|
27
|
+
- **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.
|
|
28
|
+
- **`@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.
|
|
29
|
+
|
|
30
|
+
This library fills the intersection none of them cover:
|
|
31
|
+
|
|
32
|
+
> **A long-press peek that renders any React node identically on iOS *and* Android, with no native module β presentable from anywhere, no router required.**
|
|
33
|
+
|
|
34
|
+
Concretely, reach for it when you want:
|
|
35
|
+
|
|
36
|
+
- **The same peek on both platforms.** Pure JS + Reanimated renders your content the same way on iOS and Android.
|
|
37
|
+
- **To preview arbitrary content, not a route or a menu.** A product card, an image, a profile β anything, without navigating.
|
|
38
|
+
- **No native build.** Works in Expo Go and bare React Native; no config plugin, no prebuild.
|
|
39
|
+
- **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).
|
|
40
|
+
|
|
41
|
+
**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.
|
|
42
|
+
|
|
43
|
+
## Add it to your app
|
|
44
|
+
|
|
45
|
+
**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).
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
ββ tap ββββββββββββββββββββββββββββββΊ your detail screen
|
|
49
|
+
item βββ€
|
|
50
|
+
ββ long-press ββΊ peek ββ¬β tap peek ββΊ your detail screen
|
|
51
|
+
ββ swipe / tap outside ββΊ back to the list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
### 1 Β· Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install react-native-quick-preview
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
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:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx expo install react-native-reanimated react-native-gesture-handler react-native-safe-area-context
|
|
66
|
+
npm install react-native-portalize
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Peer dependency | What it's for | Native? |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `react-native-reanimated` | The peek animation (worklets) | yes |
|
|
72
|
+
| `react-native-gesture-handler` | Long-press & swipe gestures | yes |
|
|
73
|
+
| `react-native-safe-area-context` | Sheet insets | yes |
|
|
74
|
+
| `react-native-portalize` | Renders the peek above your app | no β pure JS |
|
|
75
|
+
|
|
76
|
+
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).)
|
|
77
|
+
|
|
78
|
+
### 2 Β· Wrap your app once
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
82
|
+
import { PreviewProvider } from 'react-native-quick-preview'
|
|
83
|
+
|
|
84
|
+
export default function App() {
|
|
85
|
+
return (
|
|
86
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
87
|
+
<PreviewProvider>
|
|
88
|
+
<YourApp />
|
|
89
|
+
</PreviewProvider>
|
|
90
|
+
</GestureHandlerRootView>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3 Β· Drop it onto a list item
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// note: touchables used *inside* a preview must come from gesture-handler, not react-native
|
|
99
|
+
import { Pressable } from 'react-native-gesture-handler'
|
|
100
|
+
import { QuickPreview, QuickPreviewPressable } from 'react-native-quick-preview'
|
|
101
|
+
|
|
102
|
+
<QuickPreviewPressable
|
|
103
|
+
onPress={() => navigation.navigate('Product', { id: product.id })} // tap β full screen
|
|
104
|
+
renderPreview={() => ( // long-press β peek
|
|
105
|
+
<Pressable
|
|
106
|
+
onPress={() => {
|
|
107
|
+
QuickPreview.close()
|
|
108
|
+
navigation.navigate('Product', { id: product.id }) // tap the peek β full screen
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<ProductCard product={product} /> {/* reuse a card you already have */}
|
|
112
|
+
</Pressable>
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<Thumbnail source={product.image} />
|
|
116
|
+
</QuickPreviewPressable>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
That's the whole integration β three files touched, reusing your existing detail route as the destination.
|
|
120
|
+
|
|
121
|
+
> 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).
|
|
122
|
+
|
|
123
|
+
## Requirements
|
|
124
|
+
|
|
125
|
+
- **React Native** β₯ 0.64 (peer). Tested on RN 0.81.
|
|
126
|
+
- **React** β₯ 18.2.
|
|
127
|
+
- **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`.
|
|
128
|
+
- **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.
|
|
129
|
+
- Your app must render `<PreviewProvider>` once, inside a `<GestureHandlerRootView>` (step 2 above).
|
|
130
|
+
- `expo-haptics` is **optional** β only needed if you want haptics; pass it to `QuickPreviewPressable` via `onLongPressStart`.
|
|
131
|
+
|
|
132
|
+
## Presenting from outside components
|
|
133
|
+
|
|
134
|
+
`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).
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { QuickPreview } from 'react-native-quick-preview'
|
|
138
|
+
|
|
139
|
+
QuickPreview.present(<MyPreviewContent />, { variant: 'sheet' })
|
|
140
|
+
QuickPreview.update({ size: { maxHeight: 480 } })
|
|
141
|
+
QuickPreview.isOpen() // boolean
|
|
142
|
+
QuickPreview.close()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## QuickPreviewPressable
|
|
146
|
+
|
|
147
|
+
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:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import * as Haptics from 'expo-haptics' // optional
|
|
151
|
+
import { QuickPreviewPressable } from 'react-native-quick-preview'
|
|
152
|
+
|
|
153
|
+
<QuickPreviewPressable
|
|
154
|
+
onPress={() => router.push(`/product/${product.id}`)}
|
|
155
|
+
renderPreview={() => <ProductPreview product={product} />}
|
|
156
|
+
previewOptions={{ variant: 'popover' }}
|
|
157
|
+
delay={350} // long-press delay in ms
|
|
158
|
+
onLongPressStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)}
|
|
159
|
+
>
|
|
160
|
+
<ProductCard product={product} />
|
|
161
|
+
</QuickPreviewPressable>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`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).
|
|
165
|
+
|
|
166
|
+
## Tappable preview content
|
|
167
|
+
|
|
168
|
+
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.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// β tap may not fire inside the preview
|
|
172
|
+
import { Pressable } from 'react-native'
|
|
173
|
+
|
|
174
|
+
// β works inside the preview
|
|
175
|
+
import { Pressable } from 'react-native-gesture-handler'
|
|
176
|
+
|
|
177
|
+
present(
|
|
178
|
+
<Pressable onPress={() => { QuickPreview.close(); router.push('/detail') }}>
|
|
179
|
+
<ProductPreview product={product} />
|
|
180
|
+
</Pressable>,
|
|
181
|
+
{ variant: 'popover' }
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Scrollable previews
|
|
186
|
+
|
|
187
|
+
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.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { QuickPreviewScrollView } from 'react-native-quick-preview'
|
|
191
|
+
|
|
192
|
+
present(
|
|
193
|
+
<QuickPreviewScrollView>
|
|
194
|
+
<LongArticleContent />
|
|
195
|
+
</QuickPreviewScrollView>,
|
|
196
|
+
{ variant: 'sheet' }
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## API reference
|
|
201
|
+
|
|
202
|
+
### `present(node, options?)`
|
|
203
|
+
|
|
204
|
+
Available from `useQuickPreview()` and the static `QuickPreview`. Presents `node` as the preview content. Calling it while a preview is open replaces the content.
|
|
205
|
+
|
|
206
|
+
| Option | Type | Default | Description |
|
|
207
|
+
| --- | --- | --- | --- |
|
|
208
|
+
| `variant` | `'popover' \| 'sheet'` | `'popover'` | Centered card or bottom sheet |
|
|
209
|
+
| `size` | `'auto' \| number \| { maxHeight?, maxWidth? }` | `'auto'` | Constrains the preview container |
|
|
210
|
+
| `dismissOnBackdropPress` | `boolean` | `true` | Close when the backdrop is tapped |
|
|
211
|
+
| `dismissOnPanDown` | `boolean` | `true` | Close on swipe-down gesture |
|
|
212
|
+
| `accessibilityLabel` | `string` | | Label announced for the preview container |
|
|
213
|
+
| `onOpenStart` / `onOpenEnd` | `() => void` | | Open animation lifecycle |
|
|
214
|
+
| `onCloseStart` / `onCloseEnd` | `() => void` | | Close animation lifecycle |
|
|
215
|
+
|
|
216
|
+
### Controller
|
|
217
|
+
|
|
218
|
+
Both `useQuickPreview()` and the static `QuickPreview` expose:
|
|
219
|
+
|
|
220
|
+
| Method | Description |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `present(node, options?)` | Open a preview with the given content |
|
|
223
|
+
| `close()` | Dismiss the current preview |
|
|
224
|
+
| `update(options)` | Merge new options into the open preview |
|
|
225
|
+
| `isOpen()` | Whether a preview is currently open |
|
|
226
|
+
|
|
227
|
+
### Exports
|
|
228
|
+
|
|
229
|
+
| Export | Purpose |
|
|
230
|
+
| --- | --- |
|
|
231
|
+
| `PreviewProvider` | Hosts the preview layer; mount once at the root |
|
|
232
|
+
| `useQuickPreview` | Controller from React context (throws outside the provider) |
|
|
233
|
+
| `QuickPreview` | Static controller handle (safe no-op before mount) |
|
|
234
|
+
| `QuickPreviewPressable` | Long-press-to-preview wrapper with tap-through |
|
|
235
|
+
| `QuickPreviewScrollView` | Gesture-aware ScrollView for preview content |
|
|
236
|
+
| `QuickPreviewComponent` | Low-level headless component, for fully custom hosts |
|
|
237
|
+
| `QuickPreviewOptions`, `QuickPreviewVariant`, `QuickPreviewSize`, `QuickPreviewController` | Public TypeScript types |
|
|
238
|
+
|
|
239
|
+
## Accessibility
|
|
240
|
+
|
|
241
|
+
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.
|
|
242
|
+
|
|
243
|
+
## Example app
|
|
244
|
+
|
|
245
|
+
The [repo](https://github.com/Hashtagsmile/react-native-quick-preview) contains an Expo example app with e-commerce, article and travel preview patterns:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
git clone https://github.com/Hashtagsmile/react-native-quick-preview.git
|
|
249
|
+
cd react-native-quick-preview
|
|
250
|
+
npm install
|
|
251
|
+
npm run build
|
|
252
|
+
npm run example
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
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.
|
|
256
|
+
|
|
257
|
+
## Compatibility
|
|
258
|
+
|
|
259
|
+
| | Status |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| **Platforms** | iOS and Android (identical rendering β it's pure JS/Reanimated) |
|
|
262
|
+
| **Expo** | Works in Expo Go and dev clients. No config plugin, no prebuild, no native module. |
|
|
263
|
+
| **Bare React Native** | Supported (configure the Reanimated Babel plugin yourself). |
|
|
264
|
+
| **New Architecture** | Supported. Verified on RN 0.81 + Reanimated 4 (New-Arch-only). |
|
|
265
|
+
| **Old Architecture** | Supported via Reanimated 3. |
|
|
266
|
+
| **Web (react-native-web)** | Not a target. It may partially work, but gestures/portals aren't tested there. |
|
|
267
|
+
|
|
268
|
+
## Limitations
|
|
269
|
+
|
|
270
|
+
Be clear-eyed about what this is and isn't:
|
|
271
|
+
|
|
272
|
+
- **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)).
|
|
273
|
+
- **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.
|
|
274
|
+
- **Haptics aren't automatic.** Native context menus fire system haptics for free; here you wire them via `onLongPressStart` (one line β see `QuickPreviewPressable`).
|
|
275
|
+
- **One preview at a time.** Calling `present()` while a preview is open replaces the content; previews don't stack.
|
|
276
|
+
- **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.
|
|
277
|
+
- **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.
|
|
278
|
+
- **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.
|
|
279
|
+
|
|
280
|
+
## Versioning
|
|
281
|
+
|
|
282
|
+
This package follows semver. Release notes live in the [CHANGELOG](https://github.com/Hashtagsmile/react-native-quick-preview/blob/main/CHANGELOG.md).
|
|
283
|
+
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT Β© Oliver Lindblad
|