react-native-directional-toggle 0.1.0 → 0.1.2
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/README.en.md +237 -37
- package/README.md +237 -37
- package/lib/commonjs/AnimatedSwitch.js +204 -0
- package/lib/commonjs/AnimatedSwitch.js.map +1 -0
- package/lib/commonjs/index.js +15 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/AnimatedSwitch.js +135 -126
- package/lib/module/AnimatedSwitch.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/AnimatedSwitch.d.ts +43 -0
- package/lib/typescript/commonjs/src/AnimatedSwitch.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +4 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/module/src/AnimatedSwitch.d.ts +43 -0
- package/lib/typescript/module/src/AnimatedSwitch.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +4 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/package.json +43 -19
- package/src/AnimatedSwitch.tsx +200 -146
- package/src/index.tsx +2 -1
- package/lib/typescript/src/AnimatedSwitch.d.ts +0 -25
- package/lib/typescript/src/AnimatedSwitch.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts +0 -3
- package/lib/typescript/src/index.d.ts.map +0 -1
- /package/lib/typescript/{package.json → module/package.json} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle, type TextStyle } from 'react-native';
|
|
2
|
+
import { type WithSpringConfig, type WithTimingConfig } from 'react-native-reanimated';
|
|
3
|
+
type Option = {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
};
|
|
7
|
+
export type AnimatedSwitchProps = {
|
|
8
|
+
options: Option[];
|
|
9
|
+
value: string | number;
|
|
10
|
+
onChange: (value: string | number) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Direction of the switch.
|
|
13
|
+
* @default false (Horizontal)
|
|
14
|
+
*/
|
|
15
|
+
vertical?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Container style. Use this to set width/height.
|
|
18
|
+
*/
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
/**
|
|
21
|
+
* Style for the moving thumb.
|
|
22
|
+
*/
|
|
23
|
+
thumbStyle?: StyleProp<ViewStyle>;
|
|
24
|
+
/**
|
|
25
|
+
* Base style for option text.
|
|
26
|
+
*/
|
|
27
|
+
textStyle?: StyleProp<TextStyle>;
|
|
28
|
+
/**
|
|
29
|
+
* Style for the text when active.
|
|
30
|
+
*/
|
|
31
|
+
activeTextStyle?: StyleProp<TextStyle>;
|
|
32
|
+
/**
|
|
33
|
+
* Style for the text when inactive.
|
|
34
|
+
*/
|
|
35
|
+
inactiveTextStyle?: StyleProp<TextStyle>;
|
|
36
|
+
/**
|
|
37
|
+
* Animation configuration.
|
|
38
|
+
*/
|
|
39
|
+
animationConfig?: WithTimingConfig | WithSpringConfig;
|
|
40
|
+
};
|
|
41
|
+
export declare function AnimatedSwitch({ options, value, onChange, vertical, style, thumbStyle, textStyle, activeTextStyle, inactiveTextStyle, animationConfig, }: AnimatedSwitchProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=AnimatedSwitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnimatedSwitch.d.ts","sourceRoot":"","sources":["../../../../src/AnimatedSwitch.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAGtB,OAAiB,EAMf,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAEtB,MAAM,yBAAyB,CAAC;AAEjC,KAAK,MAAM,GAAG;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC;;OAEG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC;;OAEG;IACH,iBAAiB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC;;OAEG;IACH,eAAe,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,CAAC;CACvD,CAAC;AAQF,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,eAAmC,GACpC,EAAE,mBAAmB,2CAiIrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,eAAe,cAAc,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle, type TextStyle } from 'react-native';
|
|
2
|
+
import { type WithSpringConfig, type WithTimingConfig } from 'react-native-reanimated';
|
|
3
|
+
type Option = {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
};
|
|
7
|
+
export type AnimatedSwitchProps = {
|
|
8
|
+
options: Option[];
|
|
9
|
+
value: string | number;
|
|
10
|
+
onChange: (value: string | number) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Direction of the switch.
|
|
13
|
+
* @default false (Horizontal)
|
|
14
|
+
*/
|
|
15
|
+
vertical?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Container style. Use this to set width/height.
|
|
18
|
+
*/
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
/**
|
|
21
|
+
* Style for the moving thumb.
|
|
22
|
+
*/
|
|
23
|
+
thumbStyle?: StyleProp<ViewStyle>;
|
|
24
|
+
/**
|
|
25
|
+
* Base style for option text.
|
|
26
|
+
*/
|
|
27
|
+
textStyle?: StyleProp<TextStyle>;
|
|
28
|
+
/**
|
|
29
|
+
* Style for the text when active.
|
|
30
|
+
*/
|
|
31
|
+
activeTextStyle?: StyleProp<TextStyle>;
|
|
32
|
+
/**
|
|
33
|
+
* Style for the text when inactive.
|
|
34
|
+
*/
|
|
35
|
+
inactiveTextStyle?: StyleProp<TextStyle>;
|
|
36
|
+
/**
|
|
37
|
+
* Animation configuration.
|
|
38
|
+
*/
|
|
39
|
+
animationConfig?: WithTimingConfig | WithSpringConfig;
|
|
40
|
+
};
|
|
41
|
+
export declare function AnimatedSwitch({ options, value, onChange, vertical, style, thumbStyle, textStyle, activeTextStyle, inactiveTextStyle, animationConfig, }: AnimatedSwitchProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=AnimatedSwitch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnimatedSwitch.d.ts","sourceRoot":"","sources":["../../../../src/AnimatedSwitch.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAGtB,OAAiB,EAMf,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAEtB,MAAM,yBAAyB,CAAC;AAEjC,KAAK,MAAM,GAAG;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC;;OAEG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC;;OAEG;IACH,iBAAiB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC;;OAEG;IACH,eAAe,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,CAAC;CACvD,CAAC;AAQF,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,eAAmC,GACpC,EAAE,mBAAmB,2CAiIrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,eAAe,cAAc,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-directional-toggle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Multi-element toggle component for React Native and Expo with support for vertical and horizontal layouts and animations.",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"source": "./src/index.tsx",
|
|
6
|
+
"main": "./lib/commonjs/index.js",
|
|
7
|
+
"module": "./lib/module/index.js",
|
|
8
|
+
"types": "./lib/typescript/module/src/index.d.ts",
|
|
9
|
+
"react-native": "./src/index.tsx",
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./lib/typescript/module/src/index.d.ts",
|
|
14
|
+
"default": "./lib/module/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./lib/typescript/commonjs/src/index.d.ts",
|
|
18
|
+
"default": "./lib/commonjs/index.js"
|
|
19
|
+
}
|
|
12
20
|
},
|
|
13
21
|
"./package.json": "./package.json"
|
|
14
22
|
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
15
27
|
"files": [
|
|
16
28
|
"src",
|
|
17
29
|
"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
30
|
"!**/__tests__",
|
|
30
31
|
"!**/__fixtures__",
|
|
31
32
|
"!**/__mocks__",
|
|
@@ -33,8 +34,19 @@
|
|
|
33
34
|
],
|
|
34
35
|
"keywords": [
|
|
35
36
|
"react-native",
|
|
37
|
+
"expo",
|
|
38
|
+
"toggle",
|
|
39
|
+
"switch",
|
|
40
|
+
"animated",
|
|
41
|
+
"gesture",
|
|
36
42
|
"ios",
|
|
37
|
-
"android"
|
|
43
|
+
"android",
|
|
44
|
+
"segmented-control",
|
|
45
|
+
"multi-switch",
|
|
46
|
+
"animated-switch",
|
|
47
|
+
"react-native-component",
|
|
48
|
+
"ui-component",
|
|
49
|
+
"typescript"
|
|
38
50
|
],
|
|
39
51
|
"repository": {
|
|
40
52
|
"type": "git",
|
|
@@ -69,6 +81,9 @@
|
|
|
69
81
|
"prettier": "^2.8.8",
|
|
70
82
|
"react": "19.1.0",
|
|
71
83
|
"react-native": "0.81.5",
|
|
84
|
+
"react-native-gesture-handler": "^2.30.0",
|
|
85
|
+
"react-native-reanimated": "^4.2.1",
|
|
86
|
+
"react-native-worklets": "0.7.1",
|
|
72
87
|
"react-native-builder-bob": "^0.40.17",
|
|
73
88
|
"release-it": "^19.0.4",
|
|
74
89
|
"typescript": "^5.9.2"
|
|
@@ -87,6 +102,9 @@
|
|
|
87
102
|
"source": "src",
|
|
88
103
|
"output": "lib",
|
|
89
104
|
"targets": [
|
|
105
|
+
[
|
|
106
|
+
"commonjs"
|
|
107
|
+
],
|
|
90
108
|
[
|
|
91
109
|
"module",
|
|
92
110
|
{
|
|
@@ -113,6 +131,9 @@
|
|
|
113
131
|
"modulePathIgnorePatterns": [
|
|
114
132
|
"<rootDir>/example/node_modules",
|
|
115
133
|
"<rootDir>/lib/"
|
|
134
|
+
],
|
|
135
|
+
"transformIgnorePatterns": [
|
|
136
|
+
"node_modules/(?!(\\.pnpm/.*)?(react-native|@react-native|@react-navigation)/)"
|
|
116
137
|
]
|
|
117
138
|
},
|
|
118
139
|
"commitlint": {
|
|
@@ -150,12 +171,15 @@
|
|
|
150
171
|
],
|
|
151
172
|
"version": "0.56.1"
|
|
152
173
|
},
|
|
174
|
+
"dependencies": {},
|
|
153
175
|
"scripts": {
|
|
154
176
|
"example": "pnpm --filter react-native-directional-toggle-example run start",
|
|
155
177
|
"clean": "del-cli lib",
|
|
156
178
|
"typecheck": "tsc",
|
|
157
179
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
158
180
|
"test": "jest",
|
|
159
|
-
"release": "release-it --only-version"
|
|
181
|
+
"release": "release-it --only-version",
|
|
182
|
+
"android": "expo run:android",
|
|
183
|
+
"ios": "expo run:ios"
|
|
160
184
|
}
|
|
161
185
|
}
|
package/src/AnimatedSwitch.tsx
CHANGED
|
@@ -1,172 +1,181 @@
|
|
|
1
|
-
import { useCallback, useEffect } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type LayoutChangeEvent,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
type TextStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
12
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
4
13
|
import Animated, {
|
|
5
14
|
interpolateColor,
|
|
6
|
-
runOnJS,
|
|
7
15
|
useAnimatedStyle,
|
|
8
16
|
useSharedValue,
|
|
9
17
|
withSpring,
|
|
10
18
|
withTiming,
|
|
11
|
-
|
|
19
|
+
type WithSpringConfig,
|
|
20
|
+
type WithTimingConfig,
|
|
21
|
+
type SharedValue,
|
|
22
|
+
} from 'react-native-reanimated';
|
|
12
23
|
|
|
13
24
|
type Option = {
|
|
14
25
|
label: string;
|
|
15
26
|
value: string | number;
|
|
16
27
|
};
|
|
17
28
|
|
|
18
|
-
type
|
|
29
|
+
export type AnimatedSwitchProps = {
|
|
19
30
|
options: Option[];
|
|
20
31
|
value: string | number;
|
|
21
32
|
onChange: (value: string | number) => void;
|
|
22
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Direction of the switch.
|
|
35
|
+
* @default false (Horizontal)
|
|
36
|
+
*/
|
|
23
37
|
vertical?: boolean;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Container style. Use this to set width/height.
|
|
40
|
+
*/
|
|
41
|
+
style?: StyleProp<ViewStyle>;
|
|
42
|
+
/**
|
|
43
|
+
* Style for the moving thumb.
|
|
44
|
+
*/
|
|
45
|
+
thumbStyle?: StyleProp<ViewStyle>;
|
|
46
|
+
/**
|
|
47
|
+
* Base style for option text.
|
|
48
|
+
*/
|
|
49
|
+
textStyle?: StyleProp<TextStyle>;
|
|
50
|
+
/**
|
|
51
|
+
* Style for the text when active.
|
|
52
|
+
*/
|
|
53
|
+
activeTextStyle?: StyleProp<TextStyle>;
|
|
54
|
+
/**
|
|
55
|
+
* Style for the text when inactive.
|
|
56
|
+
*/
|
|
57
|
+
inactiveTextStyle?: StyleProp<TextStyle>;
|
|
58
|
+
/**
|
|
59
|
+
* Animation configuration.
|
|
60
|
+
*/
|
|
61
|
+
animationConfig?: WithTimingConfig | WithSpringConfig;
|
|
35
62
|
};
|
|
36
63
|
|
|
37
|
-
const
|
|
38
|
-
|
|
64
|
+
const DEFAULT_ANIMATION = {
|
|
65
|
+
duration: 150,
|
|
66
|
+
damping: 5,
|
|
67
|
+
stiffness: 100,
|
|
68
|
+
};
|
|
39
69
|
|
|
40
70
|
export function AnimatedSwitch({
|
|
41
71
|
options,
|
|
42
72
|
value,
|
|
43
73
|
onChange,
|
|
44
|
-
height = 40,
|
|
45
74
|
vertical = false,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
duration: 100,
|
|
54
|
-
damping: 50,
|
|
55
|
-
stiffness: 200,
|
|
56
|
-
},
|
|
57
|
-
}: Props) {
|
|
58
|
-
|
|
59
|
-
/** ===== SharedValues ===== */
|
|
75
|
+
style,
|
|
76
|
+
thumbStyle,
|
|
77
|
+
textStyle,
|
|
78
|
+
activeTextStyle,
|
|
79
|
+
inactiveTextStyle,
|
|
80
|
+
animationConfig = DEFAULT_ANIMATION,
|
|
81
|
+
}: AnimatedSwitchProps) {
|
|
60
82
|
const itemSizeSV = useSharedValue(0);
|
|
61
83
|
const translate = useSharedValue(0);
|
|
62
84
|
const indexSV = useSharedValue(0);
|
|
63
85
|
|
|
64
86
|
const currentIndex = options.findIndex((o) => o.value === value);
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
// Measure container layout
|
|
89
|
+
const onLayout = useCallback(
|
|
90
|
+
(e: LayoutChangeEvent) => {
|
|
91
|
+
const { width, height } = e.nativeEvent.layout;
|
|
92
|
+
|
|
93
|
+
const totalSize = vertical ? height - 4 : width - 4;
|
|
94
|
+
const itemSize = totalSize / options.length;
|
|
95
|
+
itemSizeSV.value = itemSize;
|
|
96
|
+
|
|
97
|
+
// Fix initial position on layout
|
|
98
|
+
if (currentIndex >= 0 && itemSize > 0) {
|
|
99
|
+
translate.value = currentIndex * itemSize;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[vertical, options.length, currentIndex, itemSizeSV, translate]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Sync with external value changes
|
|
69
106
|
useEffect(() => {
|
|
70
|
-
if (currentIndex >= 0) {
|
|
107
|
+
if (currentIndex >= 0 && itemSizeSV.value > 0) {
|
|
71
108
|
indexSV.value = currentIndex;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
}
|
|
109
|
+
translate.value = withTiming(
|
|
110
|
+
currentIndex * itemSizeSV.value,
|
|
111
|
+
animationConfig as WithTimingConfig
|
|
112
|
+
);
|
|
78
113
|
}
|
|
79
|
-
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [currentIndex, animationConfig]);
|
|
80
116
|
|
|
81
|
-
/** ===== JS 回调 ===== */
|
|
82
117
|
const emitChange = useCallback(
|
|
83
118
|
(index: number) => {
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
// Clamp index to safe bounds
|
|
120
|
+
const safeIndex = Math.max(0, Math.min(index, options.length - 1));
|
|
121
|
+
if (options[safeIndex]) {
|
|
122
|
+
onChange(options[safeIndex].value);
|
|
86
123
|
}
|
|
87
124
|
},
|
|
88
|
-
[onChange, options]
|
|
125
|
+
[onChange, options]
|
|
89
126
|
);
|
|
90
127
|
|
|
91
|
-
/** ===== Tap 点击切换 ===== */
|
|
92
128
|
const handlePress = (index: number) => {
|
|
129
|
+
if (itemSizeSV.value === 0) return;
|
|
93
130
|
indexSV.value = index;
|
|
94
|
-
translate.value = withTiming(
|
|
95
|
-
|
|
96
|
-
|
|
131
|
+
translate.value = withTiming(
|
|
132
|
+
index * itemSizeSV.value,
|
|
133
|
+
animationConfig as WithTimingConfig
|
|
134
|
+
);
|
|
97
135
|
emitChange(index);
|
|
98
136
|
};
|
|
99
137
|
|
|
100
|
-
/** ===== Drag 手势 ===== */
|
|
101
138
|
const panGesture = Gesture.Pan()
|
|
102
139
|
.onUpdate((e) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
140
|
+
if (itemSizeSV.value === 0) return;
|
|
141
|
+
const term = vertical ? e.translationY : e.translationX;
|
|
142
|
+
const startPos = indexSV.value * itemSizeSV.value;
|
|
143
|
+
const pos = startPos + term;
|
|
106
144
|
const max = (options.length - 1) * itemSizeSV.value;
|
|
107
|
-
|
|
108
145
|
translate.value = Math.min(Math.max(0, pos), max);
|
|
109
146
|
})
|
|
110
147
|
.onEnd(() => {
|
|
148
|
+
if (itemSizeSV.value === 0) return;
|
|
111
149
|
const index = Math.round(translate.value / itemSizeSV.value);
|
|
112
150
|
indexSV.value = index;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
runOnJS(emitChange)(index);
|
|
151
|
+
translate.value = withSpring(
|
|
152
|
+
index * itemSizeSV.value,
|
|
153
|
+
animationConfig as WithSpringConfig
|
|
154
|
+
);
|
|
155
|
+
scheduleOnRN(emitChange, index);
|
|
120
156
|
});
|
|
121
157
|
|
|
122
|
-
/** ===== Thumb 动画样式 ===== */
|
|
123
158
|
const animatedThumbStyle = useAnimatedStyle(() => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
height: itemSizeSV.value - THUMB_INSET * 2,
|
|
127
|
-
top: THUMB_INSET,
|
|
128
|
-
left: THUMB_INSET,
|
|
129
|
-
right: THUMB_INSET,
|
|
130
|
-
transform: [{ translateY: translate.value }],
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
width: itemSizeSV.value - THUMB_INSET * 2,
|
|
135
|
-
left: THUMB_INSET,
|
|
136
|
-
top: THUMB_INSET,
|
|
137
|
-
bottom: THUMB_INSET,
|
|
138
|
-
transform: [{ translateX: translate.value }],
|
|
139
|
-
};
|
|
140
|
-
});
|
|
159
|
+
// If layout not ready, hide thumb or show nothing
|
|
160
|
+
if (itemSizeSV.value === 0) return { opacity: 0 };
|
|
141
161
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const center = index * itemSizeSV.value;
|
|
146
|
-
const color = interpolateColor(
|
|
147
|
-
translate.value,
|
|
148
|
-
[center - itemSizeSV.value, center, center + itemSizeSV.value],
|
|
149
|
-
[colors.inactiveText, colors.activeText, colors.inactiveText],
|
|
150
|
-
);
|
|
151
|
-
return { color };
|
|
152
|
-
});
|
|
162
|
+
const transform = vertical
|
|
163
|
+
? [{ translateY: translate.value }]
|
|
164
|
+
: [{ translateX: translate.value }];
|
|
153
165
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const onLayout = (e: LayoutChangeEvent) => {
|
|
158
|
-
const size = vertical
|
|
159
|
-
? e.nativeEvent.layout.height
|
|
160
|
-
: e.nativeEvent.layout.width;
|
|
166
|
+
const sizeStyle = vertical
|
|
167
|
+
? { height: itemSizeSV.value, width: '100%' }
|
|
168
|
+
: { width: itemSizeSV.value, height: '100%' };
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
return {
|
|
171
|
+
position: 'absolute',
|
|
172
|
+
left: 2,
|
|
173
|
+
top: 2,
|
|
174
|
+
...sizeStyle,
|
|
175
|
+
transform,
|
|
176
|
+
opacity: 1,
|
|
177
|
+
};
|
|
178
|
+
});
|
|
170
179
|
|
|
171
180
|
return (
|
|
172
181
|
<GestureDetector gesture={panGesture}>
|
|
@@ -174,35 +183,25 @@ export function AnimatedSwitch({
|
|
|
174
183
|
onLayout={onLayout}
|
|
175
184
|
style={[
|
|
176
185
|
styles.container,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
? {
|
|
180
|
-
height: height * options.length,
|
|
181
|
-
width: VERTICAL_WIDTH,
|
|
182
|
-
flexDirection: "column",
|
|
183
|
-
}
|
|
184
|
-
: { height, flexDirection: "row" },
|
|
186
|
+
vertical ? styles.vertical : styles.horizontal,
|
|
187
|
+
style, // User override
|
|
185
188
|
]}
|
|
186
189
|
>
|
|
187
|
-
{
|
|
188
|
-
<Animated.View
|
|
189
|
-
pointerEvents="none"
|
|
190
|
-
style={[styles.thumbBase, { backgroundColor: colors.bgFront }, animatedThumbStyle]}
|
|
191
|
-
/>
|
|
190
|
+
<Animated.View style={[styles.thumb, thumbStyle, animatedThumbStyle]} />
|
|
192
191
|
|
|
193
|
-
{/* 选项列表 */}
|
|
194
192
|
{options.map((opt, index) => {
|
|
195
|
-
const labelStyle = getLabelAnimatedStyle(index);
|
|
196
193
|
return (
|
|
197
|
-
<
|
|
194
|
+
<OptionItem
|
|
198
195
|
key={opt.value}
|
|
199
|
-
|
|
196
|
+
label={opt.label}
|
|
200
197
|
onPress={() => handlePress(index)}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
index={index}
|
|
199
|
+
translate={translate}
|
|
200
|
+
itemSizeSV={itemSizeSV}
|
|
201
|
+
textStyle={textStyle}
|
|
202
|
+
activeTextStyle={activeTextStyle}
|
|
203
|
+
inactiveTextStyle={inactiveTextStyle}
|
|
204
|
+
/>
|
|
206
205
|
);
|
|
207
206
|
})}
|
|
208
207
|
</View>
|
|
@@ -210,29 +209,84 @@ export function AnimatedSwitch({
|
|
|
210
209
|
);
|
|
211
210
|
}
|
|
212
211
|
|
|
212
|
+
// Sub-component for individual options to isolate animated styles
|
|
213
|
+
const OptionItem = ({
|
|
214
|
+
label,
|
|
215
|
+
onPress,
|
|
216
|
+
index,
|
|
217
|
+
translate,
|
|
218
|
+
itemSizeSV,
|
|
219
|
+
textStyle,
|
|
220
|
+
activeTextStyle,
|
|
221
|
+
inactiveTextStyle,
|
|
222
|
+
}: {
|
|
223
|
+
label: string;
|
|
224
|
+
onPress: () => void;
|
|
225
|
+
index: number;
|
|
226
|
+
translate: SharedValue<number>;
|
|
227
|
+
itemSizeSV: SharedValue<number>;
|
|
228
|
+
textStyle?: StyleProp<TextStyle>;
|
|
229
|
+
activeTextStyle?: StyleProp<TextStyle>;
|
|
230
|
+
inactiveTextStyle?: StyleProp<TextStyle>;
|
|
231
|
+
}) => {
|
|
232
|
+
const activeColor =
|
|
233
|
+
(StyleSheet.flatten(activeTextStyle)?.color as string) ?? '#000';
|
|
234
|
+
const inactiveColor =
|
|
235
|
+
(StyleSheet.flatten(inactiveTextStyle)?.color as string) ?? '#999';
|
|
236
|
+
|
|
237
|
+
const textAnimatedStyle = useAnimatedStyle(() => {
|
|
238
|
+
const center = index * itemSizeSV.value;
|
|
239
|
+
const color = interpolateColor(
|
|
240
|
+
translate.value,
|
|
241
|
+
[center - itemSizeSV.value, center, center + itemSizeSV.value],
|
|
242
|
+
[inactiveColor, activeColor, inactiveColor]
|
|
243
|
+
);
|
|
244
|
+
return { color };
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<Pressable style={styles.option} onPress={onPress}>
|
|
249
|
+
<Animated.Text
|
|
250
|
+
style={[styles.text, textStyle, textAnimatedStyle]}
|
|
251
|
+
numberOfLines={1}
|
|
252
|
+
>
|
|
253
|
+
{label}
|
|
254
|
+
</Animated.Text>
|
|
255
|
+
</Pressable>
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
213
259
|
const styles = StyleSheet.create({
|
|
214
260
|
container: {
|
|
261
|
+
// backgroundColor: '#f0f0f0',
|
|
215
262
|
borderRadius: 16,
|
|
216
|
-
overflow:
|
|
217
|
-
|
|
263
|
+
overflow: 'hidden',
|
|
264
|
+
padding: 2,
|
|
265
|
+
// position: 'relative',
|
|
218
266
|
},
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
267
|
+
horizontal: {
|
|
268
|
+
flexDirection: 'row',
|
|
269
|
+
},
|
|
270
|
+
vertical: {
|
|
271
|
+
flexDirection: 'column',
|
|
272
|
+
},
|
|
273
|
+
thumb: {
|
|
274
|
+
// backgroundColor: '#fff',
|
|
275
|
+
borderRadius: 16,
|
|
222
276
|
shadowColor: '#000',
|
|
223
|
-
shadowOpacity: 0.3,
|
|
224
277
|
shadowOffset: { width: 0, height: 1 },
|
|
225
|
-
|
|
226
|
-
|
|
278
|
+
shadowOpacity: 0.1,
|
|
279
|
+
shadowRadius: 1,
|
|
280
|
+
elevation: 1,
|
|
227
281
|
},
|
|
228
|
-
|
|
282
|
+
option: {
|
|
229
283
|
flex: 1,
|
|
230
|
-
alignItems:
|
|
231
|
-
justifyContent:
|
|
232
|
-
zIndex: 1
|
|
284
|
+
alignItems: 'center',
|
|
285
|
+
justifyContent: 'center',
|
|
286
|
+
// zIndex: 1
|
|
233
287
|
},
|
|
234
|
-
|
|
288
|
+
text: {
|
|
235
289
|
fontSize: 14,
|
|
236
|
-
fontWeight:
|
|
290
|
+
fontWeight: '500',
|
|
237
291
|
},
|
|
238
292
|
});
|
package/src/index.tsx
CHANGED