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
63 changes: 52 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
<h1 align="center">React Native Copilot</h1>
<h1 align="center">@mr-mahabeer/react-native-copilot</h1>

<p align="center">
This is a maintained fork of react-native-copilot with bug fixes and improvements.
</p>

## 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.

<div align="center">
<p align="center">
<a href="https://github.com/mohebifar/react-native-copilot/actions/workflows/release.yml">
<img src="https://img.shields.io/github/actions/workflow/status/mohebifar/react-native-copilot/release.yml?branch=master&style=flat-square" alt="Build Status" />
</a>
<a href="https://www.npmjs.com/package/react-native-copilot">
<img src="https://img.shields.io/npm/v/react-native-copilot.svg?style=flat-square" alt="NPM Version" />
<a href="https://www.npmjs.com/package/@mr-mahabeer/react-native-copilot">
<img src="https://img.shields.io/npm/v/@mr-mahabeer/react-native-copilot.svg?style=flat-square" alt="NPM Version" />
</a>
<a href="https://www.npmjs.com/package/react-native-copilot">
<img src="https://img.shields.io/npm/dm/react-native-copilot.svg?style=flat-square" alt="NPM Downloads" />
<a href="https://www.npmjs.com/package/@mr-mahabeer/react-native-copilot">
<img src="https://img.shields.io/npm/dm/@mr-mahabeer/react-native-copilot.svg?style=flat-square" alt="NPM Downloads" />
</a>
</p>
</div>
Expand All @@ -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).
Expand All @@ -45,7 +58,7 @@ npm install --save react-native-copilot
Wrap the portion of your app that you want to use copilot with inside `<CopilotProvider>`:

```js
import { CopilotProvider } from "react-native-copilot";
import { CopilotProvider } from "@mr-mahabeer/react-native-copilot";

const AppWithCopilot = () => {
return (
Expand All @@ -65,7 +78,7 @@ import {
CopilotProvider,
CopilotStep,
walkthroughable,
} from "react-native-copilot";
} from "@mr-mahabeer/react-native-copilot";

const CopilotText = walkthroughable(Text);

Expand Down Expand Up @@ -212,6 +225,34 @@ You can customize the tooltip's arrow color:
</CopilotProvider>
```

### Additional `CopilotProvider` options

These optional props are available on `<CopilotProvider>`:

- **`stopOnOutsideClick`** (`boolean`, default `false`) — When `true`, tapping the dimmed backdrop (outside the highlighted step) ends the tour, same as skipping.

- **`labelButtonStyle`** — `StyleProp<TextStyle>` 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
<CopilotProvider
stopOnOutsideClick
shouldShowStepNumber={false}
arrowSize={0}
margin={16}
labelButtonStyle={{ fontSize: 16, fontWeight: "600" }}
>
<App />
</CopilotProvider>
```

### 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.
Expand Down Expand Up @@ -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 }) => (
<View {...copilot}>
Expand Down Expand Up @@ -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();
Expand Down
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand All @@ -29,12 +30,12 @@
"files": [
"dist"
],
"author": "Mohamad Mohebifar <mohamad@mohebifar.com>",
"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",
Expand Down
46 changes: 31 additions & 15 deletions src/components/CopilotModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export const CopilotModal = forwardRef<CopilotModalHandle, Props>(
animationDuration = 400,
tooltipComponent: TooltipComponent = Tooltip,
tooltipStyle = {},
labelButtonStyle = {},
stepNumberComponent: StepNumberComponent = StepNumber,
shouldShowStepNumber = true,
overlay = typeof NativeModules.RNSVGSvgViewManager !== "undefined"
? "svg"
: "view",
Expand All @@ -73,7 +75,7 @@ export const CopilotModal = forwardRef<CopilotModalHandle, Props>(
},
ref,
) {
const { stop, currentStep, visible } = useCopilot();
const { stop, currentStep, visible, steps } = useCopilot();
const [tooltipStyles, setTooltipStyles] = useState({});
const [arrowStyles, setArrowStyles] = useState({});
const [animatedValues] = useState({
Expand Down Expand Up @@ -153,7 +155,12 @@ export const CopilotModal = forwardRef<CopilotModalHandle, Props>(
relativeToLeft > relativeToRight ? "left" : "right";

const tooltip: ViewStyle = {};
const arrow: ViewStyle = {};
const arrow: ViewStyle = {
width: 0,
height: 0,
backgroundColor: "transparent",
borderWidth: arrowSize,
};

arrow.position = "absolute";

Expand Down Expand Up @@ -330,6 +337,9 @@ export const CopilotModal = forwardRef<CopilotModalHandle, Props>(
animationDuration={animationDuration}
backdropColor={backdropColor}
svgMaskPath={svgMaskPath}
borderRadius={
currentStep ? steps[currentStep?.name]?.maskRadius ?? 0 : 0
}
onClick={handleMaskClick}
currentStep={currentStep}
/>
Expand All @@ -342,26 +352,32 @@ export const CopilotModal = forwardRef<CopilotModalHandle, Props>(
}
return (
<>
<Animated.View
key="stepNumber"
style={[
styles.stepNumberContainer,
{
left: animatedValues.stepNumberLeft,
top: Animated.add(animatedValues.top, -STEP_NUMBER_RADIUS),
},
]}
>
<StepNumberComponent />
</Animated.View>
{shouldShowStepNumber && (
<Animated.View
key="stepNumber"
style={[
styles.stepNumberContainer,
{
left: animatedValues.stepNumberLeft,
top: Animated.add(animatedValues.top, -STEP_NUMBER_RADIUS),
},
]}
>
<StepNumberComponent />
</Animated.View>
)}

{!!arrowSize && (
<Animated.View key="arrow" style={[styles.arrow, arrowStyles]} />
)}
<Animated.View
key="tooltip"
style={[styles.tooltip, tooltipStyles, tooltipStyle]}
>
<TooltipComponent labels={labels} />
<TooltipComponent
labelButtonStyle={labelButtonStyle}
labels={labels}
/>
</Animated.View>
</>
);
Expand Down
19 changes: 14 additions & 5 deletions src/components/CopilotStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ interface Props {
text: string;
children: React.ReactElement<any>;
active?: boolean;
deleted?: boolean;
maskRadius?: number;
}

export const CopilotStep = ({
Expand All @@ -17,7 +19,10 @@ export const CopilotStep = ({
text,
children,
active = true,
deleted = false,
maskRadius = 0,
}: Props) => {
const shouldRegister = active && !deleted;
const registeredName = useRef<string | null>(null);
const { registerStep, unregisterStep } = useCopilot();
const wrapperRef = React.useRef<NativeMethods | null>(null);
Expand Down Expand Up @@ -50,7 +55,7 @@ export const CopilotStep = ({
};

useEffect(() => {
if (active) {
if (shouldRegister) {
if (registeredName.current && registeredName.current !== name) {
unregisterStep(registeredName.current);
}
Expand All @@ -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 });
Expand Down
31 changes: 23 additions & 8 deletions src/components/SvgMask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ({
Expand All @@ -37,6 +49,7 @@ export const SvgMask = ({
animated,
backdropColor,
svgMaskPath = defaultSvgPath,
borderRadius,
onClick,
currentStep,
}: MaskProps) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -140,6 +154,7 @@ export const SvgMask = ({
position: positionValue,
canvasSize,
step: currentStep,
borderRadius,
})}
/>
</Svg>
Expand Down
Loading