Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ React Native Sortables is a powerful and easy-to-use library that brings smooth,

- **Auto-scrolling** beyond screen bounds
- Customizable **layout animations** for items addition and removal
- Built-in **haptic feedback** integration (requires [react-native-haptic-feedback](https://github.com/mkuczera/react-native-haptic-feedback) dependency)
- Built-in **haptic feedback** integration via [expo-haptics](https://docs.expo.dev/versions/latest/sdk/haptics/) or [react-native-haptic-feedback](https://github.com/mkuczera/react-native-haptic-feedback)
- Different **reordering strategies** (insertion, swapping)

- 💡 **Developer Experience**
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/flex/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ Whether haptics are enabled. Vibrations are fired when the **pressed item become

:::important

To use built-in haptics, you have to install `react-native-haptic-feedback` package. See this [Getting Started](../getting-started#optional-dependencies) section for more details.
To use built-in haptics, install `expo-haptics` (Expo apps, including Expo Go) or `react-native-haptic-feedback` (bare React Native). The library auto-detects whichever is available. See this [Getting Started](../getting-started#optional-dependencies) section for more details.

You can also use any other haptics library but you will have to trigger haptics manually when callbacks are called. See the [Callbacks](#callbacks) section for more details.

Expand Down
6 changes: 4 additions & 2 deletions packages/docs/docs/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ Before getting started, you need to install and configure the following dependen

### Optional Dependencies

- **react-native-haptic-feedback**: For haptic feedback support
- Follow the installation guide in the [react-native-haptic-feedback](https://github.com/mkuczera/react-native-haptic-feedback) README
Haptic feedback is optional. The library automatically detects which haptics package is available, preferring **expo-haptics**:

- **expo-haptics**: recommended for Expo apps (already bundled in Expo Go). Install with `npx expo install expo-haptics`
- **react-native-haptic-feedback**: for bare React Native apps. Follow the installation guide in its [README](https://github.com/mkuczera/react-native-haptic-feedback)

## 2. Installation

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/grid/props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ Whether haptics are enabled. Vibrations are fired when the **pressed item become

:::important

To use built-in haptics, you have to install `react-native-haptic-feedback` package. See this [Getting Started](../getting-started#optional-dependencies) section for more details.
To use built-in haptics, install `expo-haptics` (Expo apps, including Expo Go) or `react-native-haptic-feedback` (bare React Native). The library auto-detects whichever is available. See this [Getting Started](../getting-started#optional-dependencies) section for more details.

You can also use any other haptics library but you will have to trigger haptics manually when callbacks are called. See the [Callbacks](#callbacks) section for more details.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Optional expo-haptics adapter.
*
* We never import `expo-haptics` directly, because a static import would break
* Metro bundling for bare React Native apps that don't have it installed.
* Instead we read its native module from the Expo runtime registry, so it's
* picked up automatically in any Expo app (including Expo Go) and ignored
* everywhere else.
*/

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */

import { runOnJS } from 'react-native-reanimated';

const load = () => {
const expoHaptics = (globalThis as any).expo?.modules?.ExpoHaptics;

if (!expoHaptics?.impactAsync) {
return null;
}

const impact = (style: string) => {
try {
// expo-haptics' native method is async; fire-and-forget and swallow
// rejections (e.g. when haptics are unsupported on the device)
const result = expoHaptics.impactAsync(style);
result?.catch?.(() => {
// ignore rejection
});
} catch {
// ignore
}
};

const trigger = (type = 'impactLight') => {
'worklet';
// expo-haptics runs on the JS thread, so hop over from the UI worklet
runOnJS(impact)(type === 'impactMedium' ? 'medium' : 'light');
};

return trigger;
};

const ExpoHaptics = { load };

export default ExpoHaptics;
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { default as ReactNativeHapticFeedback } from './react-native-haptic-feedback';
import ExpoHaptics from './expo-haptics';
import ReactNativeHapticFeedback from './react-native-haptic-feedback';

export const Haptics = {
// Prefer expo-haptics (available in any Expo app, including Expo Go) and
// fall back to react-native-haptic-feedback for bare React Native apps.
load: () => ExpoHaptics.load() ?? ReactNativeHapticFeedback.load()
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { HapticOptions } from 'react-native-haptic-feedback';

export const ReactNativeHapticFeedback = {
load: () => (_type: string, _options?: HapticOptions) => {
// noop
export const Haptics = {
load: () => (_type?: string) => {
// noop on web
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@ import { useCallback, useMemo } from 'react';
import { useDerivedValue } from 'react-native-reanimated';

import { IS_WEB } from '../../../constants';
import { ReactNativeHapticFeedback } from '../adapters';
import { Haptics } from '../adapters';

type HapticImpact = {
light(): void;
medium(): void;
};

let hapticFeedback: null | ReturnType<typeof ReactNativeHapticFeedback.load> =
null;
let hapticFeedback: null | ReturnType<typeof Haptics.load> = null;

export default function useHaptics(enabled: boolean): HapticImpact {
const isEnabled = !IS_WEB && enabled;
const enabledValue = useDerivedValue(() => isEnabled);

if (isEnabled && !hapticFeedback) {
hapticFeedback = ReactNativeHapticFeedback.load();
hapticFeedback = Haptics.load();
}

const light = useCallback(() => {
Expand Down
Loading