react-native-signature-input 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +147 -0
  2. package/index.d.ts +38 -0
  3. package/index.js +162 -0
  4. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # react-native-signature-input ✍️
2
+
3
+ A lightweight, **pure JavaScript** signature pad for React Native.
4
+ Built on top of **react-native-svg** for smooth, vector-based drawing without the performance cost or complexity of WebViews.
5
+
6
+ ---
7
+
8
+ ## ✨ Features
9
+
10
+ - 🚀 **Pure JS** – No native linking required (except `react-native-svg`)
11
+ - ⚡ **High Performance** – Uses `PanResponder` for smooth, lag-free drawing
12
+ - 📐 **Vector Output** – Returns SVG XML strings or raw coordinate paths
13
+ - 🎨 **Customizable** – Control stroke color, width, and background
14
+ - 📱 **Scroll Compatible** – Handles gestures correctly inside `ScrollView`
15
+
16
+ ---
17
+
18
+ ## 📦 Installation
19
+
20
+ ### Install the package
21
+ ```bash
22
+ npm install react-native-signature-input
23
+ ```
24
+
25
+ ### Install peer dependency
26
+ ```bash
27
+ npm install react-native-svg
28
+ ```
29
+
30
+ > If you are using **bare React Native**, run:
31
+ ```bash
32
+ cd ios && pod install && cd ..
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 💻 Usage
38
+
39
+ ```tsx
40
+ import React, { useRef } from 'react';
41
+ import { View, Button, Alert, StyleSheet } from 'react-native';
42
+ import SignatureInput from 'react-native-signature-input';
43
+
44
+ export default function App() {
45
+ const signatureRef = useRef(null);
46
+
47
+ const handleClear = () => {
48
+ signatureRef.current.clear();
49
+ };
50
+
51
+ const handleSave = () => {
52
+ if (signatureRef.current.isEmpty()) {
53
+ Alert.alert('Error', 'Please sign first!');
54
+ return;
55
+ }
56
+
57
+ const svgString = signatureRef.current.getSVG();
58
+ console.log(svgString);
59
+ Alert.alert('Success', 'SVG captured! Check console.');
60
+ };
61
+
62
+ return (
63
+ <View style={styles.container}>
64
+ <View style={styles.signatureContainer}>
65
+ <SignatureInput
66
+ ref={signatureRef}
67
+ height={200}
68
+ color="#000080"
69
+ strokeWidth={4}
70
+ backgroundColor="#fff"
71
+ onEnd={() => console.log('Stroke finished')}
72
+ />
73
+ </View>
74
+
75
+ <View style={styles.buttonRow}>
76
+ <Button title="Clear" onPress={handleClear} color="red" />
77
+ <Button title="Save SVG" onPress={handleSave} />
78
+ </View>
79
+ </View>
80
+ );
81
+ }
82
+
83
+ const styles = StyleSheet.create({
84
+ container: {
85
+ flex: 1,
86
+ padding: 20,
87
+ justifyContent: 'center',
88
+ backgroundColor: '#f0f0f0',
89
+ },
90
+ signatureContainer: {
91
+ borderWidth: 1,
92
+ borderColor: '#ccc',
93
+ borderRadius: 10,
94
+ overflow: 'hidden',
95
+ marginBottom: 20,
96
+ elevation: 4,
97
+ backgroundColor: 'white',
98
+ },
99
+ buttonRow: {
100
+ flexDirection: 'row',
101
+ justifyContent: 'space-around',
102
+ },
103
+ });
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 📖 API Documentation
109
+
110
+ ### Props
111
+
112
+ | Prop | Type | Default | Description |
113
+ |-----|-----|---------|-------------|
114
+ | ref | React.Ref | `null` | Required to access methods |
115
+ | height | number | `200` | Height of drawing area |
116
+ | color | string | `"black"` | Ink color |
117
+ | strokeWidth | number | `3` | Thickness of line |
118
+ | backgroundColor | string | `"#f5f5f5"` | Background color |
119
+ | onEnd | function | `undefined` | Called when stroke ends |
120
+ | onChange | function | `undefined` | Called with `(paths[])` on update |
121
+
122
+ ---
123
+
124
+ ### Methods (via Ref)
125
+
126
+ | Method | Returns | Description |
127
+ |------|--------|-------------|
128
+ | `getSVG()` | `string` | Full SVG XML string |
129
+ | `getPaths()` | `string[]` | Array of SVG path strings |
130
+ | `clear()` | `void` | Clears the canvas |
131
+ | `isEmpty()` | `boolean` | Returns true if no strokes |
132
+
133
+ ---
134
+
135
+ ## 🤝 Contributing
136
+
137
+ Pull requests are welcome!
138
+
139
+ ---
140
+
141
+ ## 📄 License
142
+
143
+ MIT
144
+
145
+ ---
146
+
147
+ **Author:** Vasanth
package/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import * as React from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+
4
+ export interface SignatureInputProps {
5
+ /** Color of the signature pen. Default: "black" */
6
+ color?: string;
7
+ /** Thickness of the pen stroke. Default: 3 */
8
+ strokeWidth?: number;
9
+ /** Height of the drawing area. Default: 200 */
10
+ height?: number | string;
11
+ /** Width of the drawing area. Default: "100%" */
12
+ width?: number | string;
13
+ /** Background color of the pad. Default: "#f5f5f5" */
14
+ backgroundColor?: string;
15
+ /** Custom container style */
16
+ style?: ViewStyle;
17
+ /** Callback when the user lifts their finger */
18
+ onEnd?: () => void;
19
+ /** Callback when the paths array changes */
20
+ onChange?: (paths: string[]) => void;
21
+ }
22
+
23
+ export interface SignatureInputRef {
24
+ /** Clears the signature pad */
25
+ clear: () => void;
26
+ /** Returns the array of SVG path strings */
27
+ getPaths: () => string[];
28
+ /** Returns the complete SVG XML string */
29
+ getSVG: () => string;
30
+ /** Returns true if no signature has been drawn */
31
+ isEmpty: () => boolean;
32
+ }
33
+
34
+ declare const SignatureInput: React.ForwardRefExoticComponent<
35
+ SignatureInputProps & React.RefAttributes<SignatureInputRef>
36
+ >;
37
+
38
+ export default SignatureInput;
package/index.js ADDED
@@ -0,0 +1,162 @@
1
+ import React, {
2
+ useState,
3
+ useRef,
4
+ useImperativeHandle,
5
+ forwardRef,
6
+ } from "react";
7
+ import { View, PanResponder, StyleSheet } from "react-native";
8
+ import Svg, { Path } from "react-native-svg";
9
+
10
+ const SignatureInput = forwardRef(
11
+ (
12
+ {
13
+ color = "black",
14
+ strokeWidth = 3,
15
+ height = 200,
16
+ width = "100%",
17
+ backgroundColor = "#f5f5f5",
18
+ style,
19
+ onEnd,
20
+ onChange,
21
+ },
22
+ ref
23
+ ) => {
24
+ const [paths, setPaths] = useState([]);
25
+ const [currentPath, setCurrentPath] = useState("");
26
+ const currentPathRef = useRef(""); // Ref to track current stroke without lag
27
+
28
+ useImperativeHandle(
29
+ ref,
30
+ () => ({
31
+ clear: () => {
32
+ setPaths([]);
33
+ setCurrentPath("");
34
+ currentPathRef.current = "";
35
+ if (onChange) onChange([]);
36
+ },
37
+ getPaths: () => paths,
38
+ isEmpty: () => paths.length === 0 && currentPathRef.current === "",
39
+
40
+ getSVG: () => {
41
+ // Ensure we use the latest 'paths' from state
42
+ const bgRect =
43
+ backgroundColor !== "transparent"
44
+ ? `<rect width="100%" height="100%" fill="${backgroundColor}" />`
45
+ : "";
46
+
47
+ const pathElements = paths
48
+ .map(
49
+ (p) =>
50
+ `<path d="${p}" stroke="${color}" stroke-width="${strokeWidth}" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`
51
+ )
52
+ .join("");
53
+
54
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
55
+ ${bgRect}
56
+ ${pathElements}
57
+ </svg>`;
58
+ },
59
+ }),
60
+ [paths, backgroundColor, color, strokeWidth, height, width]
61
+ );
62
+
63
+ const panResponder = useRef(
64
+ PanResponder.create({
65
+ onStartShouldSetPanResponder: () => true,
66
+ onStartShouldSetPanResponderCapture: () => true,
67
+ onMoveShouldSetPanResponder: () => true,
68
+ onMoveShouldSetPanResponderCapture: () => true,
69
+
70
+ onPanResponderGrant: (evt) => {
71
+ const { locationX, locationY } = evt.nativeEvent;
72
+ // Start a new path "Move To"
73
+ const startPath = `M${locationX.toFixed(1)},${locationY.toFixed(1)}`;
74
+
75
+ currentPathRef.current = startPath;
76
+ setCurrentPath(startPath);
77
+ },
78
+
79
+ onPanResponderMove: (evt) => {
80
+ const { locationX, locationY } = evt.nativeEvent;
81
+ // Add "Line To"
82
+ const newPoint = `L${locationX.toFixed(1)},${locationY.toFixed(1)}`;
83
+
84
+ currentPathRef.current += newPoint;
85
+ setCurrentPath(currentPathRef.current);
86
+ },
87
+
88
+ onPanResponderRelease: () => {
89
+ // 1. CAPTURE the full string into a local variable FIRST
90
+ const finishedPath = currentPathRef.current;
91
+
92
+ if (finishedPath) {
93
+ // 2. Use the local variable to update state (Safe from resets)
94
+ setPaths((prev) => {
95
+ const newPaths = [...prev, finishedPath];
96
+ if (onChange) onChange(newPaths);
97
+ return newPaths;
98
+ });
99
+
100
+ // 3. NOW it is safe to reset
101
+ currentPathRef.current = "";
102
+ setCurrentPath("");
103
+ if (onEnd) onEnd();
104
+ }
105
+ },
106
+ })
107
+ ).current;
108
+
109
+ return (
110
+ <View
111
+ style={[styles.container, style, { height, width, backgroundColor }]}
112
+ >
113
+ <View style={styles.touchArea} {...panResponder.panHandlers}>
114
+ <Svg style={styles.svgContainer}>
115
+ {/* Render Saved Paths */}
116
+ {paths.map((p, i) => (
117
+ <Path
118
+ key={i}
119
+ d={p}
120
+ stroke={color}
121
+ strokeWidth={strokeWidth}
122
+ fill="none"
123
+ strokeLinecap="round"
124
+ strokeLinejoin="round"
125
+ />
126
+ ))}
127
+ {/* Render Current Stroke being drawn */}
128
+ {currentPath ? (
129
+ <Path
130
+ d={currentPath}
131
+ stroke={color}
132
+ strokeWidth={strokeWidth}
133
+ fill="none"
134
+ strokeLinecap="round"
135
+ strokeLinejoin="round"
136
+ />
137
+ ) : null}
138
+ </Svg>
139
+ </View>
140
+ </View>
141
+ );
142
+ }
143
+ );
144
+
145
+ const styles = StyleSheet.create({
146
+ container: {
147
+ overflow: "hidden",
148
+ borderRadius: 8,
149
+ },
150
+ touchArea: {
151
+ flex: 1,
152
+ width: "100%",
153
+ height: "100%",
154
+ },
155
+ svgContainer: {
156
+ flex: 1,
157
+ width: "100%",
158
+ height: "100%",
159
+ },
160
+ });
161
+
162
+ export default SignatureInput;
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "react-native-signature-input",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight, pure JS signature capture component for React Native using SVG",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts"
10
+ ],
11
+ "peerDependencies": {
12
+ "react": "*",
13
+ "react-native": "*",
14
+ "react-native-svg": ">=12.0.0"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/VASANTH3105/react-native-signature-input.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/VASANTH3105/react-native-signature-input/issues"
22
+ },
23
+ "homepage": "https://github.com/VASANTH3105/react-native-signature-input#readme",
24
+ "author": "Vasanth",
25
+ "license": "MIT",
26
+ "keywords": [
27
+ "react-native",
28
+ "signature",
29
+ "handwriting",
30
+ "svg",
31
+ "canvas",
32
+ "drawing"
33
+ ]
34
+ }