├── .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 |
2 |
3 | [](https://github.com/WrathChaos/react-native-segmented-control-2)
4 |
5 | [](https://www.npmjs.com/package/react-native-segmented-control-2)
6 | [](https://www.npmjs.com/package/react-native-segmented-control-2)
7 | 
8 | [](https://opensource.org/licenses/MIT)
9 | [](https://github.com/prettier/prettier)
10 |
11 |
12 |
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 |
--------------------------------------------------------------------------------