@ariefw-rn/three-line-menu 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Arief Warazuhudien
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @ariefw-rn/three-line-menu
2
+
3
+ A lightweight, pure JavaScript UI component for React Native that provides a highly customizable 'Three-Line' (Hamburger) menu icon. Built with zero external dependencies to ensure maximum performance and seamless integration into any mobile project.
4
+
5
+ ## Features
6
+
7
+ - Three-line hamburger icon built with View components
8
+ - Pressable wrapper that toggles sidebar visibility
9
+ - Sliding sidebar that animates in from the left
10
+ - Semi-transparent overlay that covers the rest of the screen
11
+ - Tap on overlay to close the menu
12
+ - Fully customizable menu items
13
+ - Customizable icon color and size
14
+ - Smooth animations using React Native's Animated API
15
+ - TypeScript support included
16
+ - Zero external dependencies
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @ariefw-rn/three-line-menu
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```javascript
27
+ import React from 'react';
28
+ import { View, Text } from 'react-native';
29
+ import ThreeLineMenu from '@ariefw-rn/three-line-menu';
30
+
31
+ const App = () => {
32
+ const menuItems = [
33
+ { label: 'Home', id: 1 },
34
+ { label: 'Profile', id: 2 },
35
+ { label: 'Settings', id: 3 },
36
+ { label: 'About', id: 4 },
37
+ ];
38
+
39
+ const handleMenuAction = (item) => {
40
+ console.log('Menu item pressed:', item);
41
+ // Handle menu item press
42
+ };
43
+
44
+ return (
45
+ <View style={{ flex: 1 }}>
46
+ <ThreeLineMenu
47
+ items={menuItems}
48
+ onAction={handleMenuAction}
49
+ color="#000000" // Icon and text color
50
+ size={1} // Size multiplier
51
+ />
52
+ {/* Your main app content */}
53
+ </View>
54
+ );
55
+ };
56
+
57
+ export default App;
58
+ ```
59
+
60
+ ## Props
61
+
62
+ | Prop | Type | Default | Description |
63
+ |------|------|---------|-------------|
64
+ | items | Array | `[]` | Array of menu items with `label` and `id` properties |
65
+ | onAction | Function | `undefined` | Callback function when a menu item is pressed |
66
+ | color | String | `'#000'` | Color for the icon and menu text |
67
+ | size | Number | `1` | Size multiplier for the icon and sidebar |
68
+
69
+ ## MenuItem Interface
70
+
71
+ ```typescript
72
+ interface MenuItem {
73
+ label: string; // Display text for the menu item
74
+ id: string | number; // Unique identifier for the menu item
75
+ [key: string]: any; // Allow additional properties
76
+ }
77
+ ```
78
+
79
+ ## Example with all props
80
+
81
+ ```javascript
82
+ <ThreeLineMenu
83
+ items={[
84
+ { label: 'Home', id: 1 },
85
+ { label: 'Profile', id: 2 },
86
+ ]}
87
+ onAction={(item) => console.log(item)}
88
+ color="#FF6B6B"
89
+ size={1.2}
90
+ />
91
+ ```
92
+
93
+ ## Try it on CodeSandbox
94
+
95
+ You can try out the ThreeLineMenu component in your browser using the CodeSandbox example:
96
+
97
+ [![Edit @ariefw-rn/three-line-menu](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/ariefwara/rn-three-line-menu/tree/main/example)
98
+
99
+ To run the example locally:
100
+ ```bash
101
+ git clone https://github.com/ariefwara/rn-three-line-menu.git
102
+ cd rn-three-line-menu
103
+ yarn install
104
+ cd example
105
+ npm install
106
+ expo start
107
+ ```
108
+
109
+ ## Performance
110
+
111
+ - Uses native driver for animations to ensure smooth performance
112
+ - Efficient rendering with proper key props for menu items
113
+ - Minimal re-renders through proper state management
114
+ - Optimized layout with absolute positioning for overlay and sidebar
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=ThreeLineMenu.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["ThreeLineMenu.d.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+
3
+ import React, { useState, useRef } from 'react';
4
+ import { View, Pressable, StyleSheet, Animated, Text, Dimensions } from 'react-native';
5
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
6
+ const {
7
+ width: SCREEN_WIDTH
8
+ } = Dimensions.get('window');
9
+
10
+ // Define TypeScript interfaces
11
+
12
+ const ThreeLineMenu = ({
13
+ items = [],
14
+ onAction,
15
+ color = '#000',
16
+ size = 1
17
+ }) => {
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ const sidebarAnimation = useRef(new Animated.Value(-SCREEN_WIDTH)).current;
20
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
21
+ const toggleMenu = () => {
22
+ if (isOpen) {
23
+ // Close menu
24
+ Animated.parallel([Animated.timing(sidebarAnimation, {
25
+ toValue: -SCREEN_WIDTH,
26
+ duration: 300,
27
+ useNativeDriver: true
28
+ }), Animated.timing(overlayOpacity, {
29
+ toValue: 0,
30
+ duration: 300,
31
+ useNativeDriver: true
32
+ })]).start();
33
+ } else {
34
+ // Open menu
35
+ Animated.parallel([Animated.timing(sidebarAnimation, {
36
+ toValue: 0,
37
+ duration: 300,
38
+ useNativeDriver: true
39
+ }), Animated.timing(overlayOpacity, {
40
+ toValue: 0.5,
41
+ duration: 300,
42
+ useNativeDriver: true
43
+ })]).start();
44
+ }
45
+ setIsOpen(!isOpen);
46
+ };
47
+ const closeMenu = () => {
48
+ if (isOpen) {
49
+ Animated.parallel([Animated.timing(sidebarAnimation, {
50
+ toValue: -SCREEN_WIDTH,
51
+ duration: 300,
52
+ useNativeDriver: true
53
+ }), Animated.timing(overlayOpacity, {
54
+ toValue: 0,
55
+ duration: 300,
56
+ useNativeDriver: true
57
+ })]).start(() => {
58
+ setIsOpen(false);
59
+ });
60
+ }
61
+ };
62
+ const handleItemPress = item => {
63
+ if (onAction) {
64
+ onAction(item);
65
+ }
66
+ closeMenu();
67
+ };
68
+ const iconSize = 24 * size;
69
+ const sidebarWidth = SCREEN_WIDTH * 0.8 * size;
70
+ return /*#__PURE__*/_jsxs(_Fragment, {
71
+ children: [/*#__PURE__*/_jsxs(Pressable, {
72
+ onPress: toggleMenu,
73
+ style: styles.iconContainer,
74
+ children: [/*#__PURE__*/_jsx(View, {
75
+ style: [styles.line, {
76
+ backgroundColor: color,
77
+ width: iconSize,
78
+ height: iconSize / 6,
79
+ marginBottom: iconSize / 6
80
+ }]
81
+ }), /*#__PURE__*/_jsx(View, {
82
+ style: [styles.line, {
83
+ backgroundColor: color,
84
+ width: iconSize,
85
+ height: iconSize / 6,
86
+ marginBottom: iconSize / 6
87
+ }]
88
+ }), /*#__PURE__*/_jsx(View, {
89
+ style: [styles.line, {
90
+ backgroundColor: color,
91
+ width: iconSize,
92
+ height: iconSize / 6
93
+ }]
94
+ })]
95
+ }), isOpen && /*#__PURE__*/_jsx(Animated.View, {
96
+ style: [styles.overlay, {
97
+ opacity: overlayOpacity
98
+ }],
99
+ children: /*#__PURE__*/_jsx(Pressable, {
100
+ style: styles.overlay,
101
+ onPress: closeMenu
102
+ })
103
+ }), /*#__PURE__*/_jsx(Animated.View, {
104
+ style: [styles.sidebar, {
105
+ width: sidebarWidth,
106
+ transform: [{
107
+ translateX: sidebarAnimation
108
+ }]
109
+ }],
110
+ children: /*#__PURE__*/_jsx(View, {
111
+ style: styles.menuItemsContainer,
112
+ children: items.map((item, index) => /*#__PURE__*/_jsx(Pressable, {
113
+ style: ({
114
+ pressed
115
+ }) => [styles.menuItem, pressed && styles.menuItemPressed],
116
+ onPress: () => handleItemPress(item),
117
+ children: /*#__PURE__*/_jsx(Text, {
118
+ style: [styles.menuItemText, {
119
+ color
120
+ }],
121
+ children: item.label
122
+ })
123
+ }, item.id || index))
124
+ })
125
+ })]
126
+ });
127
+ };
128
+ const styles = StyleSheet.create({
129
+ iconContainer: {
130
+ padding: 10,
131
+ zIndex: 100
132
+ },
133
+ line: {
134
+ borderRadius: 2
135
+ },
136
+ overlay: {
137
+ position: 'absolute',
138
+ top: 0,
139
+ left: 0,
140
+ right: 0,
141
+ bottom: 0,
142
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
143
+ zIndex: 99
144
+ },
145
+ sidebar: {
146
+ position: 'absolute',
147
+ top: 0,
148
+ bottom: 0,
149
+ backgroundColor: '#fff',
150
+ zIndex: 101,
151
+ shadowColor: '#000',
152
+ shadowOffset: {
153
+ width: 2,
154
+ height: 0
155
+ },
156
+ shadowOpacity: 0.25,
157
+ shadowRadius: 4,
158
+ elevation: 5
159
+ },
160
+ menuItemsContainer: {
161
+ flex: 1,
162
+ paddingTop: 50,
163
+ paddingLeft: 20
164
+ },
165
+ menuItem: {
166
+ paddingVertical: 15,
167
+ paddingRight: 20
168
+ },
169
+ menuItemPressed: {
170
+ opacity: 0.7
171
+ },
172
+ menuItemText: {
173
+ fontSize: 16,
174
+ fontWeight: '500'
175
+ }
176
+ });
177
+ export default ThreeLineMenu;
178
+ //# sourceMappingURL=ThreeLineMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","useState","useRef","View","Pressable","StyleSheet","Animated","Text","Dimensions","jsx","_jsx","jsxs","_jsxs","Fragment","_Fragment","width","SCREEN_WIDTH","get","ThreeLineMenu","items","onAction","color","size","isOpen","setIsOpen","sidebarAnimation","Value","current","overlayOpacity","toggleMenu","parallel","timing","toValue","duration","useNativeDriver","start","closeMenu","handleItemPress","item","iconSize","sidebarWidth","children","onPress","style","styles","iconContainer","line","backgroundColor","height","marginBottom","overlay","opacity","sidebar","transform","translateX","menuItemsContainer","map","index","pressed","menuItem","menuItemPressed","menuItemText","label","id","create","padding","zIndex","borderRadius","position","top","left","right","bottom","shadowColor","shadowOffset","shadowOpacity","shadowRadius","elevation","flex","paddingTop","paddingLeft","paddingVertical","paddingRight","fontSize","fontWeight"],"sourceRoot":"../../src","sources":["ThreeLineMenu.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import ThreeLineMenu from "./ThreeLineMenu.js";
4
+ export default ThreeLineMenu;
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["multiply","a","b"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAO,SAASA,QAAQA,CAACC,CAAS,EAAEC,CAAS,EAAU;EACrD,OAAOD,CAAC,GAAGC,CAAC;AACd","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ interface MenuItem {
3
+ label: string;
4
+ id: string | number;
5
+ [key: string]: any;
6
+ }
7
+ interface ThreeLineMenuProps {
8
+ items: MenuItem[];
9
+ onAction?: (item: MenuItem) => void;
10
+ color?: string;
11
+ size?: number;
12
+ }
13
+ declare const ThreeLineMenu: React.FC<ThreeLineMenuProps>;
14
+ export default ThreeLineMenu;
15
+ //# sourceMappingURL=ThreeLineMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThreeLineMenu.d.ts","sourceRoot":"","sources":["../../../src/ThreeLineMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAahD,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqJ/C,CAAC;AAoDF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function multiply(a: number, b: number): number;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD"}
package/package.json ADDED
@@ -0,0 +1,165 @@
1
+ {
2
+ "name": "@ariefw-rn/three-line-menu",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight, pure JavaScript UI component for React Native that provides a highly customizable 'Three-Line' (Hamburger) menu icon. Built with zero external dependencies to ensure maximum performance and seamless integration into any mobile project.",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace @ariefw-rn/three-line-menu-example",
36
+ "clean": "del-cli lib",
37
+ "prepare": "bob build",
38
+ "typecheck": "tsc",
39
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
40
+ "test": "jest",
41
+ "release": "release-it --only-version"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/ariefwara/rn-three-line-menu.git"
51
+ },
52
+ "author": "Arief Warazuhudien <arief.wara@gmail.com> (https://www.ariefw.com)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/ariefwara/rn-three-line-menu/issues"
56
+ },
57
+ "homepage": "https://github.com/ariefwara/rn-three-line-menu#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/",
60
+ "access": "public"
61
+ },
62
+ "devDependencies": {
63
+ "@commitlint/config-conventional": "^19.8.1",
64
+ "@eslint/compat": "^1.3.2",
65
+ "@eslint/eslintrc": "^3.3.1",
66
+ "@eslint/js": "^9.35.0",
67
+ "@gorhom/portal": "^1.0.14",
68
+ "@react-native/babel-preset": "0.83.0",
69
+ "@react-native/eslint-config": "0.83.0",
70
+ "@release-it/conventional-changelog": "^10.0.1",
71
+ "@types/jest": "^29.5.14",
72
+ "@types/react": "^19.1.12",
73
+ "commitlint": "^19.8.1",
74
+ "del-cli": "^6.0.0",
75
+ "eslint": "^9.35.0",
76
+ "eslint-config-prettier": "^10.1.8",
77
+ "eslint-plugin-prettier": "^5.5.4",
78
+ "jest": "^29.7.0",
79
+ "lefthook": "^2.0.3",
80
+ "prettier": "^2.8.8",
81
+ "react": "19.1.0",
82
+ "react-native": "0.81.5",
83
+ "react-native-builder-bob": "^0.40.13",
84
+ "react-native-gesture-handler": "^2.30.0",
85
+ "react-native-reanimated": "^4.2.1",
86
+ "react-native-safe-area-context": "^5.6.2",
87
+ "release-it": "^19.0.4",
88
+ "typescript": "^5.9.3"
89
+ },
90
+ "peerDependencies": {
91
+ "react": "*",
92
+ "react-native": "*"
93
+ },
94
+ "workspaces": [
95
+ "example"
96
+ ],
97
+ "packageManager": "yarn@4.11.0",
98
+ "react-native-builder-bob": {
99
+ "source": "src",
100
+ "output": "lib",
101
+ "targets": [
102
+ [
103
+ "module",
104
+ {
105
+ "esm": true
106
+ }
107
+ ],
108
+ [
109
+ "typescript",
110
+ {
111
+ "project": "tsconfig.build.json"
112
+ }
113
+ ]
114
+ ]
115
+ },
116
+ "prettier": {
117
+ "quoteProps": "consistent",
118
+ "singleQuote": true,
119
+ "tabWidth": 2,
120
+ "trailingComma": "es5",
121
+ "useTabs": false
122
+ },
123
+ "jest": {
124
+ "preset": "react-native",
125
+ "modulePathIgnorePatterns": [
126
+ "<rootDir>/example/node_modules",
127
+ "<rootDir>/lib/"
128
+ ]
129
+ },
130
+ "commitlint": {
131
+ "extends": [
132
+ "@commitlint/config-conventional"
133
+ ]
134
+ },
135
+ "release-it": {
136
+ "git": {
137
+ "commitMessage": "chore: release ${version}",
138
+ "tagName": "v${version}"
139
+ },
140
+ "npm": {
141
+ "publish": true
142
+ },
143
+ "github": {
144
+ "release": true
145
+ },
146
+ "plugins": {
147
+ "@release-it/conventional-changelog": {
148
+ "preset": {
149
+ "name": "angular"
150
+ }
151
+ }
152
+ }
153
+ },
154
+ "create-react-native-library": {
155
+ "type": "library",
156
+ "languages": "js",
157
+ "tools": [
158
+ "eslint",
159
+ "jest",
160
+ "lefthook",
161
+ "release-it"
162
+ ],
163
+ "version": "0.56.0"
164
+ }
165
+ }
@@ -0,0 +1,18 @@
1
+ import { FC } from 'react';
2
+
3
+ interface MenuItem {
4
+ label: string;
5
+ id: string | number;
6
+ [key: string]: any; // Allow additional properties
7
+ }
8
+
9
+ interface ThreeLineMenuProps {
10
+ items: MenuItem[];
11
+ onAction?: (item: MenuItem) => void;
12
+ color?: string;
13
+ size?: number;
14
+ }
15
+
16
+ declare const ThreeLineMenu: FC<ThreeLineMenuProps>;
17
+
18
+ export default ThreeLineMenu;
@@ -0,0 +1,228 @@
1
+ import React, { useState, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Pressable,
5
+ StyleSheet,
6
+ Animated,
7
+ Text,
8
+ Dimensions,
9
+ } from 'react-native';
10
+
11
+ const { width: SCREEN_WIDTH } = Dimensions.get('window');
12
+
13
+ // Define TypeScript interfaces
14
+ interface MenuItem {
15
+ label: string;
16
+ id: string | number;
17
+ [key: string]: any; // Allow additional properties
18
+ }
19
+
20
+ interface ThreeLineMenuProps {
21
+ items: MenuItem[];
22
+ onAction?: (item: MenuItem) => void;
23
+ color?: string;
24
+ size?: number;
25
+ }
26
+
27
+ const ThreeLineMenu: React.FC<ThreeLineMenuProps> = ({
28
+ items = [],
29
+ onAction,
30
+ color = '#000',
31
+ size = 1
32
+ }) => {
33
+ const [isOpen, setIsOpen] = useState(false);
34
+ const sidebarAnimation = useRef(new Animated.Value(-SCREEN_WIDTH)).current;
35
+ const overlayOpacity = useRef(new Animated.Value(0)).current;
36
+
37
+ const toggleMenu = () => {
38
+ if (isOpen) {
39
+ // Close menu
40
+ Animated.parallel([
41
+ Animated.timing(sidebarAnimation, {
42
+ toValue: -SCREEN_WIDTH,
43
+ duration: 300,
44
+ useNativeDriver: true,
45
+ }),
46
+ Animated.timing(overlayOpacity, {
47
+ toValue: 0,
48
+ duration: 300,
49
+ useNativeDriver: true,
50
+ }),
51
+ ]).start();
52
+ } else {
53
+ // Open menu
54
+ Animated.parallel([
55
+ Animated.timing(sidebarAnimation, {
56
+ toValue: 0,
57
+ duration: 300,
58
+ useNativeDriver: true,
59
+ }),
60
+ Animated.timing(overlayOpacity, {
61
+ toValue: 0.5,
62
+ duration: 300,
63
+ useNativeDriver: true,
64
+ }),
65
+ ]).start();
66
+ }
67
+ setIsOpen(!isOpen);
68
+ };
69
+
70
+ const closeMenu = () => {
71
+ if (isOpen) {
72
+ Animated.parallel([
73
+ Animated.timing(sidebarAnimation, {
74
+ toValue: -SCREEN_WIDTH,
75
+ duration: 300,
76
+ useNativeDriver: true,
77
+ }),
78
+ Animated.timing(overlayOpacity, {
79
+ toValue: 0,
80
+ duration: 300,
81
+ useNativeDriver: true,
82
+ }),
83
+ ]).start(() => {
84
+ setIsOpen(false);
85
+ });
86
+ }
87
+ };
88
+
89
+ const handleItemPress = (item: MenuItem) => {
90
+ if (onAction) {
91
+ onAction(item);
92
+ }
93
+ closeMenu();
94
+ };
95
+
96
+ const iconSize = 24 * size;
97
+ const sidebarWidth = SCREEN_WIDTH * 0.8 * size;
98
+
99
+ return (
100
+ <>
101
+ {/* Hamburger Icon */}
102
+ <Pressable
103
+ onPress={toggleMenu}
104
+ style={styles.iconContainer}
105
+ >
106
+ <View
107
+ style={[
108
+ styles.line,
109
+ {
110
+ backgroundColor: color,
111
+ width: iconSize,
112
+ height: iconSize / 6,
113
+ marginBottom: iconSize / 6,
114
+ },
115
+ ]}
116
+ />
117
+ <View
118
+ style={[
119
+ styles.line,
120
+ {
121
+ backgroundColor: color,
122
+ width: iconSize,
123
+ height: iconSize / 6,
124
+ marginBottom: iconSize / 6,
125
+ },
126
+ ]}
127
+ />
128
+ <View
129
+ style={[
130
+ styles.line,
131
+ {
132
+ backgroundColor: color,
133
+ width: iconSize,
134
+ height: iconSize / 6,
135
+ },
136
+ ]}
137
+ />
138
+ </Pressable>
139
+
140
+ {/* Sidebar Overlay */}
141
+ {isOpen && (
142
+ <Animated.View
143
+ style={[styles.overlay, { opacity: overlayOpacity }]}
144
+ >
145
+ <Pressable style={styles.overlay} onPress={closeMenu} />
146
+ </Animated.View>
147
+ )}
148
+
149
+ {/* Sidebar */}
150
+ <Animated.View
151
+ style={[
152
+ styles.sidebar,
153
+ {
154
+ width: sidebarWidth,
155
+ transform: [{ translateX: sidebarAnimation }],
156
+ },
157
+ ]}
158
+ >
159
+ <View style={styles.menuItemsContainer}>
160
+ {items.map((item, index) => (
161
+ <Pressable
162
+ key={item.id || index}
163
+ style={({ pressed }) => [
164
+ styles.menuItem,
165
+ pressed && styles.menuItemPressed,
166
+ ]}
167
+ onPress={() => handleItemPress(item)}
168
+ >
169
+ <Text style={[styles.menuItemText, { color }]}>{item.label}</Text>
170
+ </Pressable>
171
+ ))}
172
+ </View>
173
+ </Animated.View>
174
+ </>
175
+ );
176
+ };
177
+
178
+ const styles = StyleSheet.create({
179
+ iconContainer: {
180
+ padding: 10,
181
+ zIndex: 100,
182
+ },
183
+ line: {
184
+ borderRadius: 2,
185
+ },
186
+ overlay: {
187
+ position: 'absolute',
188
+ top: 0,
189
+ left: 0,
190
+ right: 0,
191
+ bottom: 0,
192
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
193
+ zIndex: 99,
194
+ },
195
+ sidebar: {
196
+ position: 'absolute',
197
+ top: 0,
198
+ bottom: 0,
199
+ backgroundColor: '#fff',
200
+ zIndex: 101,
201
+ shadowColor: '#000',
202
+ shadowOffset: {
203
+ width: 2,
204
+ height: 0,
205
+ },
206
+ shadowOpacity: 0.25,
207
+ shadowRadius: 4,
208
+ elevation: 5,
209
+ },
210
+ menuItemsContainer: {
211
+ flex: 1,
212
+ paddingTop: 50,
213
+ paddingLeft: 20,
214
+ },
215
+ menuItem: {
216
+ paddingVertical: 15,
217
+ paddingRight: 20,
218
+ },
219
+ menuItemPressed: {
220
+ opacity: 0.7,
221
+ },
222
+ menuItemText: {
223
+ fontSize: 16,
224
+ fontWeight: '500',
225
+ },
226
+ });
227
+
228
+ export default ThreeLineMenu;
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import ThreeLineMenu from './ThreeLineMenu';
2
+
3
+ export default ThreeLineMenu;
package/src/index.tsx ADDED
@@ -0,0 +1,3 @@
1
+ export function multiply(a: number, b: number): number {
2
+ return a * b;
3
+ }