Skip to content

Commit ea04a2d

Browse files
fix: set for edge to edge
1 parent 02029b0 commit ea04a2d

File tree

12 files changed

+116
-19
lines changed

12 files changed

+116
-19
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ for the following:
3232
- [React Native](https://reactnative.dev/) >= 0.50.0
3333
- [react-native-svg](https://github.com/react-native-svg/react-native-svg) >= 12.1.0
3434

35+
## ✨ Edge-to-Edge Support
36+
37+
This library supports edge-to-edge display, including Android 15's enforced edge-to-edge mode. The tour overlay automatically covers the full screen including system bars. When using edge-to-edge layouts, you may need to adjust spotlight positioning using the `coordinateAdjustment` prop or individual `offset` props on `AttachStep` components. See the [example app](example/) for a working edge-to-edge implementation.
38+
3539
## Install
3640

3741
With `npm`:

example/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ node_modules/
7575

7676
# testing
7777
/coverage
78+
.DS_Store

example/android/app/src/main/java/com/spotlightexample/MainActivity.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
11
package com.spotlightexample
22

3+
import android.os.Build
4+
import android.os.Bundle
5+
import android.view.View
6+
import android.view.WindowInsetsController
7+
38
import com.facebook.react.ReactActivity
49
import com.facebook.react.ReactActivityDelegate
510
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
611
import com.facebook.react.defaults.DefaultReactActivityDelegate
712

813
class MainActivity : ReactActivity() {
914

15+
override fun onCreate(savedInstanceState: Bundle?) {
16+
super.onCreate(null)
17+
18+
val window = window
19+
20+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
21+
// Edge-to-edge for Android 11+
22+
window.setDecorFitsSystemWindows(false)
23+
val controller = window.insetsController
24+
controller?.systemBarsBehavior =
25+
WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
26+
} else {
27+
// Edge-to-edge for older versions
28+
@Suppress("DEPRECATION")
29+
window.decorView.systemUiVisibility = (
30+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
31+
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
32+
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
33+
)
34+
}
35+
}
36+
1037
/**
1138
* Returns the name of the main component registered from JavaScript. This is used to schedule
1239
* rendering of the component.

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"react-dom": "19.1.0",
2727
"react-is": "19.1.0",
2828
"react-native": "0.80.0",
29+
"react-native-safe-area-context": "^5.6.1",
2930
"react-native-spotlight-tour": "workspace:^",
3031
"react-native-svg": "^15.12.0",
3132
"react-native-svg-web": "^1.0.9",

example/src/App.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import dedent from "dedent";
22
import { type ReactElement, useCallback, useMemo } from "react";
3-
import { Alert, Animated, Button, Dimensions, SafeAreaView, Text, useAnimatedValue } from "react-native";
3+
import { Alert, Animated, Button, Dimensions, Platform, StatusBar, Text, View, useAnimatedValue } from "react-native";
44
import {
55
AttachStep,
6+
type RenderProps,
7+
type SpotlightTour,
68
SpotlightTourProvider,
79
TourBox,
810
type TourState,
@@ -40,7 +42,7 @@ export function App(): ReactElement {
4042
}, []);
4143

4244
const tourSteps = useMemo((): TourStep[] => [{
43-
render: ({ next, pause }) => (
45+
render: ({ next, pause }: RenderProps) => (
4446
<SpotDescriptionView>
4547
<DescriptionText>
4648
<BoldText>{"Tour: Intro section\n"}</BoldText>
@@ -59,7 +61,7 @@ export function App(): ReactElement {
5961
render: DocsTooltip,
6062
}, {
6163
arrow: true,
62-
render: props => (
64+
render: (props: RenderProps) => (
6365
<TourBox
6466
title="Tour: Customization"
6567
backText="Previous"
@@ -86,13 +88,13 @@ export function App(): ReactElement {
8688
Animated.spring(gap, {
8789
bounciness: 100,
8890
speed: 1,
89-
toValue: Dimensions.get("screen").height * 0.25,
91+
toValue: Dimensions.get("screen").height * 0.35,
9092
useNativeDriver: true,
9193
})
9294
.start(() => resolve());
9395
});
9496
},
95-
render: ({ previous, stop }) => (
97+
render: ({ previous, stop }: RenderProps) => (
9698
<SpotDescriptionView>
9799
<DescriptionText>
98100
<BoldText>{"Tour: Try it!\n"}</BoldText>
@@ -115,7 +117,12 @@ export function App(): ReactElement {
115117
}], []);
116118

117119
return (
118-
<SafeAreaView>
120+
<View style={{
121+
flex: 1,
122+
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight || 0 : 0,
123+
}}
124+
>
125+
<StatusBar translucent={true} backgroundColor="transparent" barStyle="dark-content" />
119126
<SpotlightTourProvider
120127
steps={tourSteps}
121128
overlayColor="gray"
@@ -127,15 +134,16 @@ export function App(): ReactElement {
127134
motion="bounce"
128135
shape="circle"
129136
arrow={{ color: "#B0C4DE" }}
137+
coordinateAdjustment={{ y: 50 }}
130138
>
131-
{({ resume, start, status }) => (
139+
{({ resume, start, status }: SpotlightTour) => (
132140
<>
133-
{status !== "paused" && <Button title="Start" onPress={start} />}
141+
{status !== "paused" && <View style={{ marginTop: 10 }}><Button title="Start" onPress={start} /></View>}
134142
{status === "paused" && <Button title="Resume" onPress={resume} />}
135143

136144
<SectionContainerView>
137-
<AttachStep index={1}>
138-
<TitleText>{"Introduction"}</TitleText>
145+
<AttachStep index={0}>
146+
<TitleText>{"Introduction."}</TitleText>
139147
</AttachStep>
140148
<DescriptionText>
141149
{dedent`
@@ -146,7 +154,7 @@ export function App(): ReactElement {
146154
</SectionContainerView>
147155

148156
<SectionContainerView>
149-
<AttachStep index={0}>
157+
<AttachStep index={1}>
150158
<TitleText>{"Documentation"}</TitleText>
151159
</AttachStep>
152160
<DescriptionText>
@@ -176,6 +184,6 @@ export function App(): ReactElement {
176184
</>
177185
)}
178186
</SpotlightTourProvider>
179-
</SafeAreaView>
187+
</View>
180188
);
181189
}

example/src/DocsTooltip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import dedent from "dedent";
22
import { Button } from "react-native";
3-
import { useSpotlightTour } from "react-native-spotlight-tour";
3+
import { type SpotlightTour, useSpotlightTour } from "react-native-spotlight-tour";
44

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

77
import type { ReactElement } from "react";
88

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

1313
return (
1414
<SpotDescriptionView>

package/src/lib/SpotlightTour.context.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ export interface TooltipProps {
153153
* @default 20
154154
*/
155155
arrow?: ArrowOptions | boolean | number;
156+
/**
157+
* Global coordinate adjustment for all spotlight calculations.
158+
* Useful for edge-to-edge layouts or custom status bar configurations.
159+
*/
160+
coordinateAdjustment?: CoordinateAdjustment;
156161
/**
157162
* Enables flipping the placement of the tooltip in order to keep it in view.
158163
*
@@ -271,6 +276,10 @@ export interface SpotlightTourCtx extends SpotlightTour {
271276
* @param spot the spot layout
272277
*/
273278
changeSpot: (spot: LayoutRectangle) => void;
279+
/**
280+
* Global coordinate adjustment for all spotlight calculations.
281+
*/
282+
coordinateAdjustment?: CoordinateAdjustment;
274283
/**
275284
* The spotlight layout.
276285
*/
@@ -290,6 +299,7 @@ export const ZERO_SPOT: LayoutRectangle = {
290299

291300
export const SpotlightTourContext = createContext<SpotlightTourCtx>({
292301
changeSpot: () => undefined,
302+
coordinateAdjustment: undefined,
293303
goTo: () => undefined,
294304
next: () => undefined,
295305
pause: () => undefined,
@@ -322,3 +332,14 @@ export function useSpotlightTour(): SpotlightTour {
322332
stop,
323333
};
324334
}
335+
336+
export interface CoordinateAdjustment {
337+
/**
338+
* Global X offset to apply to all spotlight calculations
339+
*/
340+
x?: number;
341+
/**
342+
* Global Y offset to apply to all spotlight calculations
343+
*/
344+
y?: number;
345+
}

package/src/lib/SpotlightTour.provider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export const SpotlightTourProvider = forwardRef<SpotlightTour, SpotlightTourProv
116116
const {
117117
arrow,
118118
children,
119+
coordinateAdjustment,
119120
flip,
120121
motion = "bounce",
121122
nativeDriver = true,
@@ -235,6 +236,7 @@ export const SpotlightTourProvider = forwardRef<SpotlightTour, SpotlightTourProv
235236

236237
const tour = useMemo((): SpotlightTourCtx => ({
237238
changeSpot,
239+
coordinateAdjustment,
238240
current,
239241
goTo,
240242
next,
@@ -246,7 +248,7 @@ export const SpotlightTourProvider = forwardRef<SpotlightTour, SpotlightTourProv
246248
status,
247249
steps,
248250
stop,
249-
}), [changeSpot, current, goTo, next, previous, spot, start, steps, stop, pause]);
251+
}), [changeSpot, coordinateAdjustment, current, goTo, next, previous, spot, start, steps, stop, pause]);
250252

251253
useImperativeHandle(ref, () => ({
252254
current,
@@ -268,6 +270,7 @@ export const SpotlightTourProvider = forwardRef<SpotlightTour, SpotlightTourProv
268270
}
269271

270272
<TourOverlay
273+
coordinateAdjustment={coordinateAdjustment}
271274
backdropOpacity={overlayOpacity}
272275
color={overlayColor}
273276
current={current}

package/src/lib/components/attach-step/AttachStep.component.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export interface AttachStepProps {
5454
* It can be a single index or multiple ones.
5555
*/
5656
index: Array<number> | number;
57+
/**
58+
* Local offset adjustment for this specific AttachStep.
59+
* This will be added to any global coordinate adjustment.
60+
*/
61+
offset?: {
62+
x?: number;
63+
y?: number;
64+
};
5765
/**
5866
* Style applied to AttachStep wrapper
5967
*/
@@ -67,8 +75,8 @@ export interface AttachStepProps {
6775
* @param props the component props
6876
* @returns an AttachStep React element
6977
*/
70-
export function AttachStep({ children, fill = false, index, style }: AttachStepProps): ReactElement {
71-
const { changeSpot, current } = useContext(SpotlightTourContext);
78+
export function AttachStep({ children, fill = false, index, offset, style }: AttachStepProps): ReactElement {
79+
const { changeSpot, coordinateAdjustment, current } = useContext(SpotlightTourContext);
7280

7381
const ref = useRef<View>(null);
7482

@@ -77,10 +85,22 @@ export function AttachStep({ children, fill = false, index, style }: AttachStepP
7785

7886
if (current !== undefined && indexes.includes(current)) {
7987
ref.current?.measureInWindow((x, y, width, height) => {
80-
changeSpot({ height, width, x, y });
88+
// Apply global coordinate adjustment
89+
const globalX = coordinateAdjustment?.x || 0;
90+
const globalY = coordinateAdjustment?.y || 0;
91+
92+
// Apply local offset
93+
const localX = offset?.x || 0;
94+
const localY = offset?.y || 0;
95+
96+
// Combine all adjustments
97+
const adjustedX = x + globalX + localX;
98+
const adjustedY = y + globalY + localY;
99+
100+
changeSpot({ height, width, x: adjustedX, y: adjustedY });
81101
});
82102
}
83-
}, [changeSpot, current, JSON.stringify(index)]);
103+
}, [changeSpot, current, JSON.stringify(index), coordinateAdjustment, offset]);
84104

85105
const onLayout = useCallback((event: LayoutChangeEvent): void => {
86106
updateSpot();

0 commit comments

Comments
 (0)