diff --git a/README.md b/README.md
index b4530f5c..29edcee1 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,28 @@
-
React Native Copilot
+@mr-mahabeer/react-native-copilot
+
+
+ This is a maintained fork of react-native-copilot with bug fixes and improvements.
+
+
+## Why this fork?
+
+- **Fixes pending issues** — Addresses open problems from the upstream project where practical.
+- **Active maintenance** — Regular updates, reviews, and releases for dependents who need a supported line.
+
+## Credits
+
+This project is based on [react-native-copilot](https://github.com/mohebifar/react-native-copilot) by Mohamad Mohebifar.
@@ -31,11 +44,11 @@
## Installation
```
-yarn add react-native-copilot
+yarn add @mr-mahabeer/react-native-copilot
# or with npm:
-npm install --save react-native-copilot
+npm install --save @mr-mahabeer/react-native-copilot
```
**Optional**: If you want to have the smooth SVG animation, you should install and link [`react-native-svg`](https://github.com/software-mansion/react-native-svg).
@@ -45,7 +58,7 @@ npm install --save react-native-copilot
Wrap the portion of your app that you want to use copilot with inside ``:
```js
-import { CopilotProvider } from "react-native-copilot";
+import { CopilotProvider } from "@mr-mahabeer/react-native-copilot";
const AppWithCopilot = () => {
return (
@@ -65,7 +78,7 @@ import {
CopilotProvider,
CopilotStep,
walkthroughable,
-} from "react-native-copilot";
+} from "@mr-mahabeer/react-native-copilot";
const CopilotText = walkthroughable(Text);
@@ -212,6 +225,34 @@ You can customize the tooltip's arrow color:
```
+### Additional `CopilotProvider` options
+
+These optional props are available on ``:
+
+- **`stopOnOutsideClick`** (`boolean`, default `false`) — When `true`, tapping the dimmed backdrop (outside the highlighted step) ends the tour, same as skipping.
+
+- **`labelButtonStyle`** — `StyleProp` applied to the default tooltip’s action labels (Skip, Previous, Next, Finish). If you use a custom `tooltipComponent`, it receives `labelButtonStyle` (and `labels`) as props—see [`TooltipProps`](https://github.com/mahabeer-dev/react-native-copilot/blob/master/src/types.ts).
+
+- **`shouldShowStepNumber`** (`boolean`, default `true`) — Set to `false` to hide the circular step-number badge.
+
+- **`arrowSize`** (`number`, default `6`) — Size of the tooltip pointer (arrow). Use `0` to hide the arrow.
+
+- **`margin`** (`number`, default `13`) — Minimum spacing between the highlighted area and the tooltip when the library positions the tooltip on screen.
+
+Example combining several options:
+
+```js
+
+
+
+```
+
### Custom overlay color
You can customize the mask color - default is `rgba(0, 0, 0, 0.4)`, by passing a color string to the `CopilotProvider` component.
@@ -284,7 +325,7 @@ const customSvgPath = (args) => {
The components wrapped inside `CopilotStep`, will receive a `copilot` prop with a mutable `ref` and `onLayou` which the outermost rendered element of the component or the element that you want the tooltip be shown around, must extend.
```js
-import { CopilotStep } from "react-native-copilot";
+import { CopilotStep } from "@mr-mahabeer/react-native-copilot";
const CustomComponent = ({ copilot }) => (
@@ -368,7 +409,7 @@ List of available events is:
**Example:**
```js
-import { useCopilot } from "react-native-copilot";
+import { useCopilot } from "@mr-mahabeer/react-native-copilot";
const HomeScreen = () => {
const { copilotEvents } = useCopilot();
diff --git a/package.json b/package.json
index 46d32076..05b1e47d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
- "name": "react-native-copilot",
- "version": "3.3.3",
- "description": "Make an interactive step by step tour guide for you react-native app",
+ "name": "@mr-mahabeer/react-native-copilot",
+ "version": "3.3.9",
+ "description": "Maintained fork of react-native-copilot with bug fixes and improvements",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"module": "dist/index.mjs",
@@ -12,11 +12,12 @@
"lint": "eslint src/ --ext .ts,.tsx",
"test": "jest",
"changeset": "changeset",
- "release": "changeset publish"
+ "release": "changeset publish",
+ "build:pack": "yarn build && npm pack"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/mohebifar/react-native-copilot.git"
+ "url": "git+https://github.com/mahabeer-dev/react-native-copilot.git"
},
"keywords": [
"react-native",
@@ -29,12 +30,12 @@
"files": [
"dist"
],
- "author": "Mohamad Mohebifar ",
+ "author": "Mr Mahabeer",
"license": "MIT",
"bugs": {
- "url": "https://github.com/mohebifar/react-native-copilot/issues"
+ "url": "https://github.com/mahabeer-dev/react-native-copilot/issues"
},
- "homepage": "https://github.com/mohebifar/react-native-copilot#readme",
+ "homepage": "https://github.com/mahabeer-dev/react-native-copilot#readme",
"devDependencies": {
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.4.3",
diff --git a/src/components/CopilotModal.tsx b/src/components/CopilotModal.tsx
index 772ab05c..e6b358fb 100644
--- a/src/components/CopilotModal.tsx
+++ b/src/components/CopilotModal.tsx
@@ -52,7 +52,9 @@ export const CopilotModal = forwardRef(
animationDuration = 400,
tooltipComponent: TooltipComponent = Tooltip,
tooltipStyle = {},
+ labelButtonStyle = {},
stepNumberComponent: StepNumberComponent = StepNumber,
+ shouldShowStepNumber = true,
overlay = typeof NativeModules.RNSVGSvgViewManager !== "undefined"
? "svg"
: "view",
@@ -73,7 +75,7 @@ export const CopilotModal = forwardRef(
},
ref,
) {
- const { stop, currentStep, visible } = useCopilot();
+ const { stop, currentStep, visible, steps } = useCopilot();
const [tooltipStyles, setTooltipStyles] = useState({});
const [arrowStyles, setArrowStyles] = useState({});
const [animatedValues] = useState({
@@ -153,7 +155,12 @@ export const CopilotModal = forwardRef(
relativeToLeft > relativeToRight ? "left" : "right";
const tooltip: ViewStyle = {};
- const arrow: ViewStyle = {};
+ const arrow: ViewStyle = {
+ width: 0,
+ height: 0,
+ backgroundColor: "transparent",
+ borderWidth: arrowSize,
+ };
arrow.position = "absolute";
@@ -330,6 +337,9 @@ export const CopilotModal = forwardRef(
animationDuration={animationDuration}
backdropColor={backdropColor}
svgMaskPath={svgMaskPath}
+ borderRadius={
+ currentStep ? steps[currentStep?.name]?.maskRadius ?? 0 : 0
+ }
onClick={handleMaskClick}
currentStep={currentStep}
/>
@@ -342,18 +352,21 @@ export const CopilotModal = forwardRef(
}
return (
<>
-
-
-
+ {shouldShowStepNumber && (
+
+
+
+ )}
+
{!!arrowSize && (
)}
@@ -361,7 +374,10 @@ export const CopilotModal = forwardRef(
key="tooltip"
style={[styles.tooltip, tooltipStyles, tooltipStyle]}
>
-
+
>
);
diff --git a/src/components/CopilotStep.tsx b/src/components/CopilotStep.tsx
index e0cdb588..6dfb4bbf 100644
--- a/src/components/CopilotStep.tsx
+++ b/src/components/CopilotStep.tsx
@@ -9,6 +9,8 @@ interface Props {
text: string;
children: React.ReactElement;
active?: boolean;
+ deleted?: boolean;
+ maskRadius?: number;
}
export const CopilotStep = ({
@@ -17,7 +19,10 @@ export const CopilotStep = ({
text,
children,
active = true,
+ deleted = false,
+ maskRadius = 0,
}: Props) => {
+ const shouldRegister = active && !deleted;
const registeredName = useRef(null);
const { registerStep, unregisterStep } = useCopilot();
const wrapperRef = React.useRef(null);
@@ -50,7 +55,7 @@ export const CopilotStep = ({
};
useEffect(() => {
- if (active) {
+ if (shouldRegister) {
if (registeredName.current && registeredName.current !== name) {
unregisterStep(registeredName.current);
}
@@ -61,27 +66,31 @@ export const CopilotStep = ({
measure,
wrapperRef,
visible: true,
+ maskRadius,
});
registeredName.current = name;
+ } else if (registeredName.current) {
+ unregisterStep(registeredName.current);
+ registeredName.current = null;
}
- }, [name, order, text, registerStep, unregisterStep, active]);
+ }, [name, order, text, registerStep, unregisterStep, shouldRegister, maskRadius]);
useEffect(() => {
- if (active) {
+ if (shouldRegister) {
return () => {
if (registeredName.current) {
unregisterStep(registeredName.current);
}
};
}
- }, [name, unregisterStep, active]);
+ }, [name, unregisterStep, shouldRegister]);
const copilotProps = useMemo(
() => ({
ref: wrapperRef,
onLayout: () => {}, // Android hack
}),
- []
+ [],
);
return React.cloneElement(children, { copilot: copilotProps });
diff --git a/src/components/SvgMask.tsx b/src/components/SvgMask.tsx
index 7b9359e8..0d066bbf 100644
--- a/src/components/SvgMask.tsx
+++ b/src/components/SvgMask.tsx
@@ -17,15 +17,27 @@ const defaultSvgPath: SvgMaskPathFunction = ({
size,
position,
canvasSize,
+ borderRadius: br = 0,
}): string => {
- const positionX = (position.x as any)._value as number;
- const positionY = (position.y as any)._value as number;
- const sizeX = (size.x as any)._value as number;
- const sizeY = (size.y as any)._value as number;
+ const x = (position.x as any)._value as number;
+ const y = (position.y as any)._value as number;
+ const w = (size.x as any)._value as number;
+ const h = (size.y as any)._value as number;
- return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${positionX},${positionY}H${
- positionX + sizeX
- }V${positionY + sizeY}H${positionX}V${positionY}Z`;
+ const r = Math.max(0, Math.min(br, w / 2, h / 2));
+
+ if (r <= 0) {
+ return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${x},${y}H${x + w}V${y + h}H${x}V${y}Z`;
+ }
+
+ return [
+ `M0,0H${canvasSize.x}V${canvasSize.y}H0V0Z`,
+ `M${x + r},${y}`,
+ `H${x + w - r}A${r},${r} 0 0 1 ${x + w},${y + r}`,
+ `V${y + h - r}A${r},${r} 0 0 1 ${x + w - r},${y + h}`,
+ `H${x + r}A${r},${r} 0 0 1 ${x},${y + h - r}`,
+ `V${y + r}A${r},${r} 0 0 1 ${x + r},${y}Z`,
+ ].join("");
};
export const SvgMask = ({
@@ -37,6 +49,7 @@ export const SvgMask = ({
animated,
backdropColor,
svgMaskPath = defaultSvgPath,
+ borderRadius,
onClick,
currentStep,
}: MaskProps) => {
@@ -58,12 +71,13 @@ export const SvgMask = ({
position: positionValue,
canvasSize,
step: currentStep,
+ borderRadius,
});
if (maskRef.current) {
maskRef.current.setNativeProps({ d });
}
- }, [canvasSize, currentStep, svgMaskPath, positionValue, sizeValue]);
+ }, [canvasSize, currentStep, svgMaskPath, positionValue, sizeValue, borderRadius]);
const animate = useCallback(
(toSize: ValueXY = size, toPosition: ValueXY = position) => {
@@ -140,6 +154,7 @@ export const SvgMask = ({
position: positionValue,
canvasSize,
step: currentStep,
+ borderRadius,
})}
/>
diff --git a/src/components/default-ui/Tooltip.tsx b/src/components/default-ui/Tooltip.tsx
index a624f712..85120a77 100644
--- a/src/components/default-ui/Tooltip.tsx
+++ b/src/components/default-ui/Tooltip.tsx
@@ -8,7 +8,7 @@ import { styles } from "../style";
import type { TooltipProps } from "../../types";
import { useCopilot } from "../../contexts/CopilotProvider";
-export const Tooltip = ({ labels }: TooltipProps) => {
+export const Tooltip = ({ labels, labelButtonStyle = {} }: TooltipProps) => {
const { goToNext, goToPrev, stop, currentStep, isFirstStep, isLastStep } =
useCopilot();
@@ -33,21 +33,21 @@ export const Tooltip = ({ labels }: TooltipProps) => {
{!isLastStep ? (
- {labels.skip}
+ {labels.skip}
) : null}
{!isFirstStep ? (
- {labels.previous}
+ {labels.previous}
) : null}
{!isLastStep ? (
- {labels.next}
+ {labels.next}
) : (
- {labels.finish}
+ {labels.finish}
)}
diff --git a/src/components/style.ts b/src/components/style.ts
index 13ced248..20b11957 100644
--- a/src/components/style.ts
+++ b/src/components/style.ts
@@ -18,7 +18,9 @@ export const styles = StyleSheet.create({
},
arrow: {
position: "absolute",
- borderWidth: ARROW_SIZE,
+ width: 0,
+ height: 0,
+ backgroundColor: "transparent",
},
tooltip: {
position: "absolute",
diff --git a/src/contexts/CopilotProvider.tsx b/src/contexts/CopilotProvider.tsx
index 331c2735..57d2b691 100644
--- a/src/contexts/CopilotProvider.tsx
+++ b/src/contexts/CopilotProvider.tsx
@@ -16,7 +16,7 @@ import {
import { OFFSET_WIDTH } from "../components/style";
import { useStateWithAwait } from "../hooks/useStateWithAwait";
import { useStepsMap } from "../hooks/useStepsMap";
-import { type CopilotOptions, type Step } from "../types";
+import type { StepsMap, CopilotOptions, Step } from "../types";
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type Events = {
@@ -31,7 +31,7 @@ interface CopilotContextType {
currentStep: Step | undefined;
start: (
fromStep?: string,
- suppliedScrollView?: ScrollView | null
+ suppliedScrollView?: ScrollView | null,
) => Promise;
stop: () => Promise;
goToNext: () => Promise;
@@ -43,6 +43,7 @@ interface CopilotContextType {
isLastStep: boolean;
currentStepNumber: number;
totalStepsNumber: number;
+ steps: StepsMap;
}
/*
@@ -96,7 +97,7 @@ export const CopilotProvider = ({
y: size.y - OFFSET_WIDTH / 2 + verticalOffset,
});
},
- [verticalOffset]
+ [verticalOffset],
);
const setCurrentStep = useCallback(
@@ -112,7 +113,7 @@ export const CopilotProvider = ({
(_x, y, _w, h) => {
const yOffset = y > 0 ? y - h / 2 : 0;
scrollView.scrollTo({ y: yOffset, animated: false });
- }
+ },
);
}
}
@@ -123,10 +124,10 @@ export const CopilotProvider = ({
void moveModalToStep(step);
}
},
- scrollView != null ? 100 : 0
+ scrollView != null ? 100 : 0,
);
},
- [copilotEvents, moveModalToStep, scrollView, setCurrentStepState]
+ [copilotEvents, moveModalToStep, scrollView, setCurrentStepState],
);
const start = useCallback(
@@ -163,7 +164,7 @@ export const CopilotProvider = ({
setCurrentStep,
setVisibility,
steps,
- ]
+ ],
);
const stop = useCallback(async () => {
@@ -179,7 +180,7 @@ export const CopilotProvider = ({
async (n: number) => {
await setCurrentStep(getNthStep(n));
},
- [getNthStep, setCurrentStep]
+ [getNthStep, setCurrentStep],
);
const prev = useCallback(async () => {
@@ -202,6 +203,7 @@ export const CopilotProvider = ({
isLastStep,
currentStepNumber,
totalStepsNumber,
+ steps,
}),
[
registerStep,
@@ -218,16 +220,14 @@ export const CopilotProvider = ({
isLastStep,
currentStepNumber,
totalStepsNumber,
- ]
+ steps,
+ ],
);
return (
<>
-
+
{children}
>
diff --git a/src/types.ts b/src/types.ts
index 9ba8c31f..d8b33ea8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -2,6 +2,8 @@ import type {
Animated,
LayoutRectangle,
NativeMethods,
+ StyleProp,
+ TextStyle,
ViewStyle,
} from "react-native";
@@ -14,6 +16,7 @@ export interface Step {
wrapperRef: React.RefObject;
measure: () => Promise;
text: string;
+ maskRadius?: number;
}
export interface CopilotContext {
@@ -32,6 +35,7 @@ export type SvgMaskPathFunction = (args: {
position: Animated.ValueXY;
canvasSize: ValueXY;
step: Step;
+ borderRadius?: number;
}) => string;
export type StepsMap = Record;
@@ -44,6 +48,7 @@ export type Labels = Partial<
export interface TooltipProps {
labels: Labels;
+ labelButtonStyle?: StyleProp;
}
export interface MaskProps {
@@ -55,6 +60,7 @@ export interface MaskProps {
animated: boolean;
backdropColor: string;
svgMaskPath?: SvgMaskPathFunction;
+ borderRadius: number;
layout: {
width: number;
height: number;
@@ -69,15 +75,17 @@ export interface CopilotOptions {
animationDuration?: number;
tooltipComponent?: React.ComponentType;
tooltipStyle?: ViewStyle;
- stepNumberComponent?: React.ComponentType;
+ stepNumberComponent?: React.ComponentType;
animated?: boolean;
labels?: Labels;
androidStatusBarVisible?: boolean;
svgMaskPath?: SvgMaskPathFunction;
verticalOffset?: number;
arrowColor?: string;
- arrowSize?: number
- margin?: number
+ arrowSize?: number;
+ margin?: number;
stopOnOutsideClick?: boolean;
backdropColor?: string;
+ labelButtonStyle?: StyleProp;
+ shouldShowStepNumber?: boolean;
}
diff --git a/tsup.config.ts b/tsup.config.ts
index 5933a4f7..58c5ff07 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -12,11 +12,11 @@ export default defineConfig({
async onSuccess() {
if (process.env.NODE_ENV === "development") {
const exampleOutputPath = path.resolve(
- "./example/node_modules/react-native-copilot"
+ "./example/node_modules/react-native-copilot",
);
const exampleOutputNodeModulesPath = path.resolve(
exampleOutputPath,
- "node_modules"
+ "node_modules",
);
await Promise.all(
@@ -24,7 +24,7 @@ export default defineConfig({
const outputPath = path.resolve(exampleOutputPath, file);
console.log("Copying file: ", file, "to ->", outputPath);
await fs.copyFile(file, outputPath);
- })
+ }),
);
await fs.rm(exampleOutputNodeModulesPath, {
recursive: true,