├── .commitlintrc.json ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc ├── README.md ├── assets ├── Screenshots │ └── react-native-segmented-control-2.gif └── logo.png ├── example ├── .gitignore ├── App.tsx ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── add.png │ ├── bolt.png │ ├── book.png │ ├── eyeglasses.png │ ├── favicon.png │ ├── glasses.png │ ├── history.png │ ├── icon.png │ ├── minus-button.png │ ├── search.png │ ├── splash-icon.png │ ├── stars-2.png │ └── stars.png ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── lib ├── SegmentedControl.style.ts └── SegmentedControl.tsx ├── package-lock.json ├── package.json └── tsconfig.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "header-max-length": [0, "always", 150], 5 | "subject-case": [0, "always", "sentence-case"], 6 | "type-enum": [ 7 | 2, 8 | "always", 9 | [ 10 | "ci", 11 | "chore", 12 | "docs", 13 | "feat", 14 | "fix", 15 | "perf", 16 | "refactor", 17 | "revert", 18 | "style", 19 | "test" 20 | ] 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "@react-native-community", 8 | "prettier", 9 | ], 10 | ignorePatterns: [ 11 | "**/*/*.js", 12 | "*.js", 13 | "*.svg", 14 | "*.json", 15 | "*.png", 16 | "package.json", 17 | "package-lock.json", 18 | ], 19 | parser: "@typescript-eslint/parser", 20 | plugins: [ 21 | "import", 22 | "react", 23 | "react-native", 24 | "prettier", 25 | "react-hooks", 26 | "@typescript-eslint", 27 | "promise", 28 | "jest", 29 | "unused-imports", 30 | ], 31 | env: { 32 | browser: true, 33 | es2021: true, 34 | "jest/globals": true, 35 | "react-native/react-native": true, 36 | }, 37 | settings: { 38 | "import/resolver": { 39 | node: { 40 | extensions: [ 41 | ".js", 42 | ".jsx", 43 | ".ts", 44 | ".tsx", 45 | ".d.ts", 46 | ".android.js", 47 | ".android.jsx", 48 | ".android.ts", 49 | ".android.tsx", 50 | ".ios.js", 51 | ".ios.jsx", 52 | ".ios.ts", 53 | ".ios.tsx", 54 | ".web.js", 55 | ".web.jsx", 56 | ".web.ts", 57 | ".web.tsx", 58 | ], 59 | }, 60 | }, 61 | }, 62 | rules: { 63 | quotes: [ 64 | "error", 65 | "double", 66 | { 67 | avoidEscape: true, 68 | }, 69 | ], 70 | "import/extensions": [ 71 | "error", 72 | "never", 73 | { 74 | svg: "always", 75 | model: "always", 76 | style: "always", 77 | png: "always", 78 | jpg: "always", 79 | json: "always", 80 | constant: "always", 81 | }, 82 | ], 83 | "no-useless-catch": 0, 84 | "react-hooks/exhaustive-deps": 0, 85 | "max-len": ["error", 120], 86 | "@typescript-eslint/ban-ts-comment": 1, 87 | "@typescript-eslint/no-empty-function": 0, 88 | "@typescript-eslint/no-explicit-any": 1, 89 | "@typescript-eslint/explicit-module-boundary-types": 0, 90 | "react/jsx-filename-extension": ["error", { extensions: [".tsx"] }], 91 | "react-native/no-unused-styles": 2, 92 | "react-native/split-platform-components": 2, 93 | "react-native/no-inline-styles": 0, 94 | "react-native/no-color-literals": 0, 95 | "react-native/no-raw-text": 0, 96 | "import/no-extraneous-dependencies": 2, 97 | "import/no-named-as-default-member": 2, 98 | "import/order": 0, 99 | "import/no-duplicates": 2, 100 | "import/no-useless-path-segments": 2, 101 | "import/no-cycle": 2, 102 | "import/prefer-default-export": 0, 103 | "import/no-anonymous-default-export": 0, 104 | "import/named": 0, 105 | "@typescript-eslint/no-empty-interface": 0, 106 | "import/namespace": 0, 107 | "import/default": 0, 108 | "import/no-named-as-default": 0, 109 | "import/no-unused-modules": 0, 110 | "import/no-deprecated": 0, 111 | "@typescript-eslint/indent": 0, 112 | "react-hooks/rules-of-hooks": 2, 113 | camelcase: 2, 114 | "prefer-destructuring": 2, 115 | "no-nested-ternary": 2, 116 | "prettier/prettier": [ 117 | "error", 118 | { 119 | endOfLine: "auto", 120 | }, 121 | ], 122 | }, 123 | }; 124 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # Visual Studio Code 33 | # 34 | .vscode/ 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | !debug.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | */fastlane/report.xml 56 | */fastlane/Preview.html 57 | */fastlane/screenshots 58 | 59 | # Bundle artifact 60 | *.jsbundle 61 | 62 | # CocoaPods 63 | /ios/Pods/ 64 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run prettier 5 | npm run lint 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node Modules 2 | **/node_modules 3 | node_modules 4 | # Example 5 | example 6 | # Assets 7 | Assets 8 | assets -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/** -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "singleQuote": false, 5 | "trailingComma": "all", 6 | "tabWidth": 2, 7 | "semi": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Native Segmented Control 2 2 | 3 | [![React Native Segmented Control 2](https://img.shields.io/badge/-%F0%9F%9A%80%20React%20Native%20Segmented%20Control%2C%20Pure%20Javascript%20for%20iOS%20and%20Android-orange?style=for-the-badge)](https://github.com/WrathChaos/react-native-segmented-control-2) 4 | 5 | [![npm version](https://img.shields.io/npm/v/react-native-segmented-control-2.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-segmented-control-2) 6 | [![npm](https://img.shields.io/npm/dt/react-native-segmented-control-2.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-segmented-control-2) 7 | ![Platform - Android and iOS](https://img.shields.io/badge/platform-Android%20%7C%20iOS-blue.svg?style=for-the-badge) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) 9 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=for-the-badge)](https://github.com/prettier/prettier) 10 | 11 |

12 | React Native Segmented Control 2 14 |

15 | 16 | # Installation 17 | 18 | Add the dependency: 19 | 20 | ```bash 21 | npm i react-native-segmented-control-2 22 | ``` 23 | 24 | ## Peer Dependencies 25 | 26 | Zero Dependency 🥳 27 | 28 | # Usage 29 | 30 | ## Import 31 | 32 | ```jsx 33 | import SegmentedControl from "react-native-segmented-control-2"; 34 | ``` 35 | 36 | ## Fundamental Usage 37 | 38 | ```jsx 39 | console.log("Index: ", index)} 42 | /> 43 | ``` 44 | 45 | As controlled component: 46 | 47 | ```jsx 48 | const [index, setIndex] = useState(0) 49 | return ( 50 | 55 | ) 56 | ``` 57 | 58 | ## Customized Usage 59 | 60 | ```jsx 61 | console.log("Index: ", index)} 67 | /> 68 | ``` 69 | 70 | ### Any Component Usage 71 | 72 | You can use the segmented control with any component. 73 | All you need to do is that put any component into the `tabs` props. 74 | Please check out the `example` for its usage 75 | 76 | ## Example Project 😍 77 | 78 | You can checkout the example project 🥰 79 | 80 | Simply run 81 | 82 | - `npm i` 83 | - `react-native run-ios/android` 84 | 85 | should work of the example project. 86 | 87 | # Configuration - Props 88 | 89 | ## Fundamentals 90 | 91 | | Property | Type | Default | Description | 92 | | -------- | :------: | :-------: | ---------------------------------------------------- | 93 | | tabs | any[] | undefined | set the array for tabs | 94 | | onChange | function | undefined | set your own logic when the tab is pressed / changed | 95 | | value | number | undefined | value of index if used as a controlled component | 96 | 97 | ## Customization (Optionals) 98 | 99 | | Property | Type | Default | Description | 100 | |------------------|:---------:|:-------:|------------------------------------------------------------| 101 | | style | ViewStyle | default | set or override the style object for the main container | 102 | | initialIndex | number | 0 | set the initial index | 103 | | activeTextColor | string | #000 | change the active tab's text color | 104 | | activeTabColor | string | #FFF | change the active tab's color | 105 | | gap | number | 0 | set extra spacing for animation horizontal value | 106 | | tabStyle | ViewStyle | default | set or override the style object for the tab | 107 | | selectedTabStyle | ViewStyle | default | set or override the style object for the selected tab | 108 | | activeTextStyle | TextStyle | default | set or override the style object for the active tab's text | 109 | | textStyle | TextStyle | default | set or override the style object for tab's text | 110 | 111 | ## Future Plans 112 | 113 | - [x] ~~LICENSE~~ 114 | - [x] ~~Controller component support~~ (Thanks to @madox2 (https://github.com/WrathChaos/react-native-segmented-control-2/pull/6)) 115 | - [x] ~~Getting rid of screen width and manual width dependencies~~ (Thanks to @philo23 (https://github.com/WrathChaos/react-native-segmented-control-2/pull/7) 116 | - [ ] Write an article about the lib on Medium 117 | 118 | ## Credits 119 | 120 | Heavily inspired by these libraries: 121 | 122 | - [react-native-segmented-control/segmented-control](https://github.com/react-native-segmented-control/segmented-control) 123 | - [Karthik-B-06/react-native-segmented-control](https://github.com/Karthik-B-06/react-native-segmented-control) 124 | 125 | I created this library because they're really not maintain actively and this is a pure javascript written library with a lot of customizations and better code structure 126 | 127 | ## Author 128 | 129 | FreakyCoder, kurayogun@gmail.com 130 | 131 | ## License 132 | 133 | React Native Segmented Control 2 is available under the MIT license. See the LICENSE file for more info. 134 | -------------------------------------------------------------------------------- /assets/Screenshots/react-native-segmented-control-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/assets/Screenshots/react-native-segmented-control-2.gif -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/assets/logo.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Image, Dimensions, Text } from "react-native"; 3 | import SegmentedControl from "react-native-segmented-control-2"; 4 | 5 | const { width: ScreenWidth } = Dimensions.get("screen"); 6 | 7 | const book = require("./assets/book.png"); 8 | const glasses = require("./assets/glasses.png"); 9 | const history = require("./assets/history.png"); 10 | 11 | const App = () => { 12 | const [currentIndex, setCurrentIndex] = React.useState(0); 13 | const [selectedGPT, setSelectedGPT] = React.useState(1); 14 | 15 | const tabColors = ["red", "green"]; 16 | 17 | const renderAdd = () => ( 18 | 27 | ); 28 | const renderMinus = () => ( 29 | 38 | ); 39 | 40 | const renderImage = (imageSource: any) => ( 41 | 49 | ); 50 | 51 | const renderGPT3 = () => ( 52 | 55 | 65 | GPT-3.5 66 | 67 | ); 68 | 69 | 70 | const renderGPT4o = () => ( 71 | 74 | 84 | GPT-4o 85 | 86 | ); 87 | 88 | 89 | // @ts-ignore 90 | return ( 91 | 92 | setCurrentIndex(index)} 98 | /> 99 | console.log("Index: ", index)} 104 | /> 105 | setSelectedGPT(index)} 111 | /> 112 | console.log("Index: ", index)} 116 | /> 117 | console.log("Index: ", index)} 123 | /> 124 | console.log("Index: ", index)} 130 | /> 131 | console.log("Index: ", index)} 137 | /> 138 | 139 | ); 140 | }; 141 | 142 | export default App; 143 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "example", 4 | "slug": "example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "newArchEnabled": true, 10 | "splash": { 11 | "image": "./assets/splash-icon.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "android": { 19 | "adaptiveIcon": { 20 | "foregroundImage": "./assets/adaptive-icon.png", 21 | "backgroundColor": "#ffffff" 22 | } 23 | }, 24 | "web": { 25 | "favicon": "./assets/favicon.png" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /example/assets/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/add.png -------------------------------------------------------------------------------- /example/assets/bolt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/bolt.png -------------------------------------------------------------------------------- /example/assets/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/book.png -------------------------------------------------------------------------------- /example/assets/eyeglasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/eyeglasses.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/glasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/glasses.png -------------------------------------------------------------------------------- /example/assets/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/history.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/minus-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/minus-button.png -------------------------------------------------------------------------------- /example/assets/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/search.png -------------------------------------------------------------------------------- /example/assets/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/splash-icon.png -------------------------------------------------------------------------------- /example/assets/stars-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/stars-2.png -------------------------------------------------------------------------------- /example/assets/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WrathChaos/react-native-segmented-control-2/df137d8deec2672aac24199bf54421aa3e4c6612/example/assets/stars.png -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | 3 | import App from './App'; 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in Expo Go or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App); 9 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.ts", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~52.0.41", 13 | "expo-status-bar": "~2.0.1", 14 | "react": "18.3.1", 15 | "react-native": "0.76.7", 16 | "react-native-segmented-control-2": "^2.1.0" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.25.2", 20 | "@types/react": "~18.3.12", 21 | "typescript": "^5.3.3" 22 | }, 23 | "private": true 24 | } 25 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/SegmentedControl.style.ts: -------------------------------------------------------------------------------- 1 | import { Animated, StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | display: "flex", 6 | width: "90%", 7 | flexDirection: "row", 8 | alignItems: "center", 9 | borderRadius: 8, 10 | backgroundColor: "#F3F5F6", 11 | }, 12 | tabsContainer: { 13 | flex: 1, 14 | flexDirection: "row", 15 | }, 16 | tab: { 17 | flex: 1, 18 | paddingVertical: 8, // iOS Default 19 | alignItems: "center", 20 | justifyContent: "center", 21 | }, 22 | activeTab: ( 23 | tabWidth: number, 24 | gap: number, 25 | activeTabColor: string, 26 | slideAnimation: Animated.Value, 27 | ) => ({ 28 | ...StyleSheet.absoluteFillObject, 29 | width: tabWidth, 30 | margin: gap, 31 | backgroundColor: activeTabColor, 32 | transform: [{ translateX: slideAnimation }], 33 | borderRadius: 6, 34 | shadowColor: "#000", 35 | shadowOpacity: 0.2, 36 | shadowRadius: 3, 37 | shadowOffset: { 38 | width: 0, 39 | height: 2, 40 | }, 41 | elevation: 4, 42 | }), 43 | textStyle: { 44 | fontSize: 14, // iOS Default 45 | textAlign: "center", 46 | fontWeight: "500", 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /lib/SegmentedControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from "react"; 2 | import { 3 | View, 4 | Text, 5 | Animated, 6 | StyleProp, 7 | ViewStyle, 8 | TouchableOpacity, 9 | TextStyle, 10 | I18nManager, 11 | LayoutChangeEvent, 12 | LayoutRectangle, 13 | } from "react-native"; 14 | import styles from "./SegmentedControl.style"; 15 | 16 | interface SegmentedControlProps { 17 | tabs: any[]; 18 | initialIndex?: number; 19 | activeTextColor?: string; 20 | activeTabColor?: string; 21 | gap?: number; 22 | style?: StyleProp; 23 | tabStyle?: StyleProp | ((index: number) => StyleProp); 24 | textStyle?: StyleProp; 25 | selectedTabStyle?: StyleProp; 26 | onChange: (index: number) => void; 27 | value?: number; 28 | } 29 | 30 | const SegmentedControl: React.FC = ({ 31 | style, 32 | tabs, 33 | onChange, 34 | value, 35 | tabStyle, 36 | textStyle, 37 | selectedTabStyle, 38 | initialIndex = 0, 39 | gap = 2, 40 | activeTextColor = "#000", 41 | activeTabColor = "#fff", 42 | }) => { 43 | const [slideAnimation, _] = useState(new Animated.Value(0)); 44 | const [localCurrentIndex, setCurrentIndex] = useState(initialIndex); 45 | const [tabLayouts, setTabLayouts] = useState<{ 46 | [tabIndex: number]: LayoutRectangle; 47 | }>({}); 48 | 49 | const currentIndex = value ?? localCurrentIndex; 50 | 51 | const handleTabPress = useCallback( 52 | (index: number) => { 53 | setCurrentIndex(index); 54 | onChange && onChange(index); 55 | }, 56 | [onChange], 57 | ); 58 | 59 | useEffect(() => { 60 | Animated.spring(slideAnimation, { 61 | toValue: 62 | (I18nManager.isRTL ? -1 : 1) * (tabLayouts[currentIndex]?.x || 0), 63 | stiffness: 180, 64 | damping: 25, 65 | mass: 1, 66 | useNativeDriver: true, 67 | }).start(); 68 | }, [currentIndex, slideAnimation, tabLayouts]); 69 | 70 | const onLayoutTab = useCallback( 71 | (index: number, { nativeEvent }: LayoutChangeEvent) => { 72 | setTabLayouts((prev) => ({ ...prev, [index]: nativeEvent.layout })); 73 | }, 74 | [], 75 | ); 76 | 77 | const tabSpecificStyle = useCallback( 78 | (tabIndex: number) => { 79 | if (typeof tabStyle === "function") { 80 | return tabStyle(tabIndex); 81 | } 82 | 83 | return tabStyle; 84 | }, 85 | [tabStyle], 86 | ); 87 | 88 | const renderSelectedTab = useCallback( 89 | () => ( 90 | 101 | ), 102 | [activeTabColor, gap, selectedTabStyle, slideAnimation, tabLayouts], 103 | ); 104 | 105 | const renderTab = (tab: any, index: number) => { 106 | const isActiveTab = currentIndex === index; 107 | const isTabText = typeof tab === "string"; 108 | return ( 109 | handleTabPress(index)} 114 | onLayout={(e) => onLayoutTab(index, e)} 115 | > 116 | {!isTabText ? ( 117 | tab 118 | ) : ( 119 | 127 | {tab} 128 | 129 | )} 130 | 131 | ); 132 | }; 133 | 134 | return ( 135 | 136 | {renderSelectedTab()} 137 | 138 | {tabs.map((tab, index: number) => renderTab(tab, index))} 139 | 140 | 141 | ); 142 | }; 143 | 144 | export default SegmentedControl; 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-segmented-control-2", 3 | "version": "2.1.0", 4 | "description": "🚀 React Native Segmented Control, Pure Javascript for iOS and Android", 5 | "main": "./build/dist/SegmentedControl.js", 6 | "repository": "git@github.com:WrathChaos/react-native-segmented-control-2.git", 7 | "author": "FreakyCoder ", 8 | "license": "MIT", 9 | "homepage": "https://www.freakycoder.com", 10 | "bugs": "https://github.com/WrathChaos/react-native-segmented-control-2/issues", 11 | "keywords": [ 12 | "segmented-control", 13 | "segmented-control-2", 14 | "FreakyCoder", 15 | "freakycoder", 16 | "kuray", 17 | "Kuray", 18 | "react", 19 | "react-native", 20 | "javascript", 21 | "ui-lib", 22 | "rn" 23 | ], 24 | "devDependencies": { 25 | "@commitlint/cli": "^15.0.0", 26 | "@commitlint/config-conventional": "^15.0.0", 27 | "@react-native-community/eslint-config": "^2.0.0", 28 | "@types/react": "^16.9.53", 29 | "@types/react-native": "^0.63.25", 30 | "eslint": "^7.11.0", 31 | "eslint-config-airbnb": "^18.2.0", 32 | "husky": "^7.0.0", 33 | "lint-staged": "^10.4.2", 34 | "npm-post-install": "0.0.2", 35 | "prettier": "^2.1.2", 36 | "prettier-format": "^3.0.3", 37 | "react-native-typescript-transformer": "^1.2.13", 38 | "typescript": "^4.0.3" 39 | }, 40 | "scripts": { 41 | "build": "cd lib && tsc && cp ../package.json ../build/dist/ && echo Build completed!", 42 | "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", 43 | "lint": "tslint -p tsconfig.json", 44 | "version": "npm run format && git add -A src", 45 | "postversion": "git push && git push --tags", 46 | "prettier": "cd lib && npx prettier --write . && git add .", 47 | "prepare": "husky install", 48 | "husky:setup": "npx husky-init && npm run husky:commitlint && npm run husky:prettier && npm run husky:lint", 49 | "husky:commitlint": "npx husky add .husky/commit-msg 'npx --no-install commitlint --edit'", 50 | "husky:prettier": "npx husky set .husky/pre-commit 'npm run prettier'", 51 | "husky:lint": "npx husky add .husky/pre-commit 'npm run lint'" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "es6", 5 | "lib": ["es6"], 6 | "allowJs": true, 7 | "jsx": "react-native", 8 | "noImplicitAny": false, 9 | "incremental": true /* Enable incremental compilation */, 10 | "isolatedModules": true, 11 | "strict": true, 12 | "moduleResolution": "node", 13 | "baseUrl": "./", 14 | "outDir": "build/dist", 15 | "noEmitHelpers": true, 16 | "alwaysStrict": true, 17 | "strictFunctionTypes": true, 18 | "resolveJsonModule": true, 19 | "importHelpers": false, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "allowSyntheticDefaultImports": true, 23 | "strictNullChecks": true, 24 | "skipDefaultLibCheck": true, 25 | "skipLibCheck": true, 26 | "esModuleInterop": true, 27 | "typeRoots": ["./node_modules/@types", "./@types"], 28 | "declaration": true /* Generates corresponding '.d.ts' file. */, 29 | "sourceMap": true /* Generates corresponding '.map' file. */ 30 | }, 31 | "exclude": [ 32 | "example", 33 | "example-manual-state", 34 | "node_modules", 35 | "babel.config.js", 36 | "metro.config.js", 37 | "jest.config.js" 38 | ] 39 | } 40 | --------------------------------------------------------------------------------