Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
node-version: [22.x, 24.x]

steps:
- uses: actions/checkout@v5
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to `react-native-quick-preview` are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [2.1.0] - 2026-11-04

### Added

- **Context-menu actions.** `actions: [{ title, onPress, icon?, destructive?, disabled? }]` renders a menu under the popover, or at the bottom of the sheet, on iOS and Android. Pressing an action closes the preview, then runs it. `QuickPreviewPressable` takes `actions` directly.
- **Grow from the item.** The popover morphs out of the pressed item's on-screen rect instead of fading in at the center. `QuickPreviewPressable` measures the item automatically (opt out with `animateFromItem={false}`); imperative callers can pass `origin: { x, y, width, height }`.
- **Theming and dark mode.** `colorScheme: 'light' | 'dark' | 'system'`, `containerStyle`, `backdropColor`, and `renderBackdrop` (e.g. a blur view). The default stays light, so existing apps look the same.
- **`PreviewProvider defaultOptions`** sets app-wide defaults; options passed to `present()` override them.
- New public types: `QuickPreviewAction`, `QuickPreviewColorScheme`, `QuickPreviewOrigin`.

### Changed

- **`react-native-portalize` is no longer a peer dependency.** It's unmaintained (last release 2022); the provider now renders its own overlay, and the headless `QuickPreviewComponent` portals into it. Apps can uninstall portalize. Keeping it installed is harmless.
- Supports Expo SDK 58 / React Native 0.88, Reanimated 4.7 and Gesture Handler 3. The library's dev setup and the example app are on SDK 58; Jest now uses `@react-native/jest-preset`.
- The `react-native` peer range is now `>=0.72` (was `>=0.64`, which the Reanimated 3+ / Gesture Handler 2+ requirement never actually allowed).

### Notes

- With Gesture Handler 3, a missing `<GestureHandlerRootView>` now throws in development. The library has always required it; GH2 just failed silently.

## [2.0.2] - 2026-07-06

### Fixed
Expand Down Expand Up @@ -90,6 +110,7 @@ Initial public release.

- `<QuickPreview>` controlled modal component with fade/scale animations, swipe-to-close, backdrop press handling, light/dark theme, and long-press-to-open examples.

[2.1.0]: https://github.com/Hashtagsmile/react-native-quick-preview/compare/v2.0.2...v2.1.0
[2.0.2]: https://github.com/Hashtagsmile/react-native-quick-preview/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/Hashtagsmile/react-native-quick-preview/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/Hashtagsmile/react-native-quick-preview/compare/v1.0.5...v2.0.0
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
[![Try it in Expo Snack](https://img.shields.io/badge/Expo_Snack-Try_it_live-4630EB?logo=expo&logoColor=white)](https://snack.expo.dev/@oliver.at.sellpy/react-native-quick-preview)

Long-press an item to peek at its content, tap outside or swipe to dismiss. A headless quick-preview library for React Native: mount one `PreviewProvider`, then `present()` any content from a hook or a static handle.
**The iOS long-press peek, on Android too.** Long-press an item and a preview grows out of it, with an optional context menu. It renders the same on iOS and Android, shows any React content, and works in Expo Go because there's no native code. Mount one `PreviewProvider`, then `present()` from a hook or a static handle.

**Why it exists:** the long-press peek is well-served on iOS (Expo Router's `Link.Preview`, native context-menu libraries) but those are all iOS-only, and most preview a route or a menu rather than arbitrary content. This library covers the intersection none of them do — *a long-press peek that renders any React node identically on iOS **and** Android, with no native module, presentable from anywhere, no router required.* See [Why use this](packages/react-native-quick-preview/README.md#why-use-this) for the honest comparison, including when to reach for a native library instead.
**Why it exists:** the long-press peek and context menu are well served on iOS (Expo Router's `Link.Preview`, zeego, `react-native-ios-context-menu`), but those are all iOS-only, and most preview a route rather than arbitrary content. This library covers what none of them do: *a peek with a context menu that renders any React node identically on iOS **and** Android, with no native module, presentable from anywhere, no router required.* See [How it compares](packages/react-native-quick-preview/README.md#how-it-compares) for the table, including when to reach for a native library instead.

This is the development monorepo. **If you just want to use the library, read the [package README](packages/react-native-quick-preview/README.md)** for installation, quick start and the full API. It's the same document you see on [npm](https://www.npmjs.com/package/react-native-quick-preview).

Expand Down Expand Up @@ -43,7 +43,7 @@ This is the development monorepo. **If you just want to use the library, read th

## Development

Requires Node 20+ and npm 10 (npm workspaces).
Requires Node 22.13+ (React Native 0.88 tooling) and npm 10 (npm workspaces).

```bash
git clone https://github.com/Hashtagsmile/react-native-quick-preview.git
Expand Down
4 changes: 2 additions & 2 deletions apps/expo-quick-preview-example/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function TabsLayout() {
options={{
title: 'Examples',
tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? 'sparkles' : 'sparkles-outline'} size={22} color={color} />
<Ionicons name={focused ? 'sparkles' : 'sparkles-outline'} size={22} color={color as string} />
),
}}
/>
Expand All @@ -27,7 +27,7 @@ export default function TabsLayout() {
options={{
title: 'API',
tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? 'code-slash' : 'code-slash-outline'} size={24} color={color} />
<Ionicons name={focused ? 'code-slash' : 'code-slash-outline'} size={24} color={color as string} />
),
}}
/>
Expand Down
17 changes: 14 additions & 3 deletions apps/expo-quick-preview-example/app/examples/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Social feed — an Instagram-style Explore grid. Long-press a thumbnail to peek
// the full post, tap the peek to open it. The grid is where the peek earns its
// keep: a thumbnail tells you little, the peek gives you the whole post.
// keep: a thumbnail tells you little, the peek gives you the whole post. The
// peek grows out of the tapped tile and carries a context menu — on iOS and
// Android alike.
import React from 'react'
import { View, StyleSheet, ScrollView, Image, Dimensions } from 'react-native'
import { View, StyleSheet, ScrollView, Image, Dimensions, Share } from 'react-native'
import { Pressable } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import * as Haptics from 'expo-haptics'
import * as Clipboard from 'expo-clipboard'
import { QuickPreview, QuickPreviewPressable } from 'react-native-quick-preview'
import { posts, type Item } from '../../data/examples'
import { PostPreview } from '../../components/PostPreview'
Expand All @@ -15,7 +18,9 @@ import { colors, spacing } from '../../theme'

const { width } = Dimensions.get('window')
const GAP = 2
const TILE = (width - GAP * 2) / 3
// Floor: three exact thirds can sum past the row width after layout rounding,
// which wraps the third tile onto the next line.
const TILE = Math.floor((width - GAP * 2) / 3)

export default function Feed() {
const router = useRouter()
Expand All @@ -31,6 +36,12 @@ export default function Feed() {
onPress={() => router.push({ pathname: '/detail', params: { id: post.id } })}
renderPreview={() => <PeekWrapper id={post.id} item={post} />}
previewOptions={{ variant: 'popover', accessibilityLabel: `Preview ${post.username}'s post` }}
actions={[
{ title: 'Open post', onPress: () => router.push({ pathname: '/detail', params: { id: post.id } }) },
{ title: 'Share', onPress: () => Share.share({ message: `Check out ${post.username}'s post` }) },
{ title: 'Copy link', onPress: () => Clipboard.setStringAsync(`https://example.com/p/${post.id}`) },
{ title: 'Report', destructive: true, onPress: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning) },
]}
onLongPressStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)}
style={styles.tile}
testID={`post-${post.id}`}
Expand Down
2 changes: 1 addition & 1 deletion apps/expo-quick-preview-example/app/examples/shop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { colors, radius, spacing } from '../../theme'

const { width } = Dimensions.get('window')
const COL_GAP = spacing.md
const CARD_W = (width - spacing.lg * 2 - COL_GAP) / 2
const CARD_W = Math.floor((width - spacing.lg * 2 - COL_GAP) / 2)

const money = (p?: number | string) =>
p === undefined ? '' : typeof p === 'number' ? `$${p.toFixed(0)}` : p
Expand Down
26 changes: 13 additions & 13 deletions apps/expo-quick-preview-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@
"web": "expo start --web"
},
"dependencies": {
"expo": "^54.0.0",
"expo-clipboard": "~8.0.8",
"expo-haptics": "~15.0.8",
"expo-router": "~6.0.24",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-portalize": "^1.0.7",
"@expo/vector-icons": "^15.0.2",
"expo": "~58.0.23",
"expo-clipboard": "~58.0.4",
"expo-haptics": "~58.0.7",
"expo-router": "~58.0.30",
"expo-status-bar": "~58.0.4",
"react": "19.3.0",
"react-native": "0.88.1",
"react-native-gesture-handler": "~3.3.0",
"react-native-quick-preview": "file:../../packages/react-native-quick-preview",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-worklets": "0.5.1"
"react-native-reanimated": "4.7.1",
"react-native-safe-area-context": "~5.11.1",
"react-native-screens": "~4.30.1",
"react-native-worklets": "0.13.0"
}
}
Loading
Loading