diff --git a/.gitignore b/.gitignore index 2ba1fc0..7efecc7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ local.properties # Packages *.tgz +.DS_Store diff --git a/README.md b/README.md index f05ef4c..35823ea 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,30 @@ for the following: - [React Native](https://reactnative.dev/) >= 0.50.0 - [react-native-svg](https://github.com/react-native-svg/react-native-svg) >= 12.1.0 +## ✨ Edge-to-Edge Support + +This library supports edge-to-edge display (including Android 15’s enforced mode). The tour overlay already covers the whole screen, system bars included. + +When your screens use a translucent status bar you can enable proper handling—and optionally nudge the spotlight—via the new `translucentStatusBar` prop: + +```tsx + + {/* … */} + +``` + +`insets.top` is the height of the status bar returned by `react-native-safe-area-context`. Any value that represents the actual status-bar height will work. + +`translucentStatusBar` is **optional**—if you omit it or set `enable` to `false`, the tour continues to behave exactly as before. + +See the [example app](example/) for a fully-working edge-to-edge implementation. + ## Install With `npm`: diff --git a/example/android/app/src/main/java/com/spotlightexample/MainActivity.kt b/example/android/app/src/main/java/com/spotlightexample/MainActivity.kt index 824e98f..9271be6 100644 --- a/example/android/app/src/main/java/com/spotlightexample/MainActivity.kt +++ b/example/android/app/src/main/java/com/spotlightexample/MainActivity.kt @@ -1,5 +1,10 @@ package com.spotlightexample +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.WindowInsetsController + import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled @@ -7,6 +12,28 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate class MainActivity : ReactActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(null) + + val window = window + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Edge-to-edge for Android 11+ + window.setDecorFitsSystemWindows(false) + val controller = window.insetsController + controller?.systemBarsBehavior = + WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + } else { + // Edge-to-edge for older versions + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + ) + } + } + /** * Returns the name of the main component registered from JavaScript. This is used to schedule * rendering of the component. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 0787479..67346ce 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1648,6 +1648,96 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket + - react-native-safe-area-context (5.6.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common (= 5.6.1) + - react-native-safe-area-context/fabric (= 5.6.1) + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-safe-area-context/common (5.6.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-safe-area-context/fabric (5.6.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - React-NativeModulesApple (0.80.0): - boost - DoubleConversion @@ -2226,6 +2316,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -2347,6 +2438,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" React-NativeModulesApple: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: @@ -2455,6 +2548,7 @@ SPEC CHECKSUMS: React-logger: b69e65dc60f768e5509ac0cc27a360124bf70478 React-Mapbuffer: b48f9f3311fd0ec0f7a5dc39d707eff521fb5f38 React-microtasksnativemodule: d8568d0485a350c720c061ae835e09fc88c28715 + react-native-safe-area-context: fd72bd1eb562ff2a0fda20e9a7828c4b0cabf5a4 React-NativeModulesApple: f10596688a03af66804cfbe61792be24a7888da8 React-oscompat: 7c0a341cc31e350da71ddf2e46de0a845d1d1626 React-perflogger: 4cc44451f694d6205f47bd8d5d87c9c862c3335c @@ -2488,8 +2582,8 @@ SPEC CHECKSUMS: ReactCommon: 658874decaf8c4fd76cfa3a878b94a869db85b1c RNSVG: c73af7848d94ca3e8136a5191d055e3c1d6fedab SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 395b5d614cd7cbbfd76b05d01bd67230a6ad004e + Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb PODFILE CHECKSUM: 9742088751b8c39a438474c638da6274a8066e53 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/example/package.json b/example/package.json index 2bbb898..e36ed41 100644 --- a/example/package.json +++ b/example/package.json @@ -26,6 +26,7 @@ "react-dom": "19.1.0", "react-is": "19.1.0", "react-native": "0.80.0", + "react-native-safe-area-context": "^5.6.1", "react-native-spotlight-tour": "workspace:^", "react-native-svg": "^15.12.0", "react-native-svg-web": "^1.0.9", diff --git a/example/src/App.tsx b/example/src/App.tsx index 465cea1..42eb1b5 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,8 +1,11 @@ import dedent from "dedent"; import { type ReactElement, useCallback, useMemo } from "react"; -import { Alert, Animated, Button, Dimensions, SafeAreaView, Text, useAnimatedValue } from "react-native"; +import { Alert, Animated, Button, Dimensions, Text, useAnimatedValue } from "react-native"; +import { SafeAreaProvider, SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context"; import { AttachStep, + type RenderProps, + type SpotlightTour, SpotlightTourProvider, TourBox, type TourState, @@ -19,9 +22,9 @@ import { } from "./App.styles"; import { DocsTooltip } from "./DocsTooltip"; -export function App(): ReactElement { +function AppContent(): ReactElement { const gap = useAnimatedValue(0, { useNativeDriver: true }); - + const { top } = useSafeAreaInsets(); const showSummary = useCallback(({ index, isLast }: TourState) => { Alert.alert( "Tour Finished", @@ -40,7 +43,7 @@ export function App(): ReactElement { }, []); const tourSteps = useMemo((): TourStep[] => [{ - render: ({ next, pause }) => ( + render: ({ next, pause }: RenderProps) => ( {"Tour: Intro section\n"} @@ -59,7 +62,7 @@ export function App(): ReactElement { render: DocsTooltip, }, { arrow: true, - render: props => ( + render: (props: RenderProps) => ( resolve()); }); }, - render: ({ previous, stop }) => ( + flip: true, + placement: "top", + render: ({ previous, stop }: RenderProps) => ( {"Tour: Try it!\n"} @@ -115,11 +120,11 @@ export function App(): ReactElement { }], []); return ( - + - {({ resume, start, status }) => ( + {({ resume, start, status }: SpotlightTour) => ( <> {status !== "paused" &&