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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ local.properties

# Packages
*.tgz
.DS_Store
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<SpotlightTourProvider
translucentStatusBar={{
enable: true, // overlay under system bars
coordinateAdjustment: { y: insets.top }, // y = status-bar height (e.g. use SafeArea insets.top)
}}
{...otherProps}
>
{/* … */}
</SpotlightTourProvider>
```

`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`:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
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
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.
Expand Down
98 changes: 96 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 29 additions & 12 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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",
Expand All @@ -40,7 +43,7 @@ export function App(): ReactElement {
}, []);

const tourSteps = useMemo((): TourStep[] => [{
render: ({ next, pause }) => (
render: ({ next, pause }: RenderProps) => (
<SpotDescriptionView>
<DescriptionText>
<BoldText>{"Tour: Intro section\n"}</BoldText>
Expand All @@ -59,7 +62,7 @@ export function App(): ReactElement {
render: DocsTooltip,
}, {
arrow: true,
render: props => (
render: (props: RenderProps) => (
<TourBox
title="Tour: Customization"
backText="Previous"
Expand Down Expand Up @@ -92,7 +95,9 @@ export function App(): ReactElement {
.start(() => resolve());
});
},
render: ({ previous, stop }) => (
flip: true,
placement: "top",
render: ({ previous, stop }: RenderProps) => (
<SpotDescriptionView>
<DescriptionText>
<BoldText>{"Tour: Try it!\n"}</BoldText>
Expand All @@ -115,27 +120,31 @@ export function App(): ReactElement {
}], []);

return (
<SafeAreaView>
<SafeAreaView style={{ flex: 1 }}>
<SpotlightTourProvider
steps={tourSteps}
overlayColor="gray"
overlayOpacity={0.36}
overlayOpacity={0.86}
nativeDriver={true}
onBackdropPress="continue"
onStop={showSummary}
onPause={alertPause}
motion="bounce"
shape="circle"
arrow={{ color: "#B0C4DE" }}
translucentStatusBar={{
coordinateAdjustment: { y: top },
enable: true,
}}
>
{({ resume, start, status }) => (
{({ resume, start, status }: SpotlightTour) => (
<>
{status !== "paused" && <Button title="Start" onPress={start} />}
{status === "paused" && <Button title="Resume" onPress={resume} />}

<SectionContainerView>
<AttachStep index={1}>
<TitleText>{"Introduction"}</TitleText>
<AttachStep index={0}>
<TitleText>{"Introduction."}</TitleText>
</AttachStep>
<DescriptionText>
{dedent`
Expand All @@ -146,7 +155,7 @@ export function App(): ReactElement {
</SectionContainerView>

<SectionContainerView>
<AttachStep index={0}>
<AttachStep index={1}>
<TitleText>{"Documentation"}</TitleText>
</AttachStep>
<DescriptionText>
Expand Down Expand Up @@ -179,3 +188,11 @@ export function App(): ReactElement {
</SafeAreaView>
);
}

export function App(): ReactElement {
return (
<SafeAreaProvider>
<AppContent />
</SafeAreaProvider>
);
}
4 changes: 2 additions & 2 deletions example/src/DocsTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import dedent from "dedent";
import { Button } from "react-native";
import { useSpotlightTour } from "react-native-spotlight-tour";
import { type SpotlightTour, useSpotlightTour } from "react-native-spotlight-tour";

import { BoldText, ButtonsGroupView, DescriptionText, SpotDescriptionView } from "./App.styles";

import type { ReactElement } from "react";

export function DocsTooltip(): ReactElement {
// You can also use the hook instead of the props here!
const { next, previous } = useSpotlightTour();
const { next, previous }: SpotlightTour = useSpotlightTour();

return (
<SpotDescriptionView>
Expand Down
Loading