1import { useEffect, useState } from "react";
2import { I18nManager, Platform, Pressable, View } from "react-native";
3import Animated, {
4 Easing,
5 ReduceMotion,
6 useAnimatedStyle,
7 useSharedValue,
8 withTiming,
9} from "react-native-reanimated";
10import { AppText as Text } from "./AppText";
11import { cn } from "../lib/cn";
12import { MaterialSegmentedControl } from "./MaterialSegmentedControl";
13import type { SegmentedControlProps } from "./SegmentedControl.types";
14
15export type { SegmentedControlProps } from "./SegmentedControl.types";
16
17export function SegmentedControl<Value extends number | string>(
18 props: SegmentedControlProps<Value>,
19) {
20 const compact = props.size === "compact";
21 const [width, setWidth] = useState(0);
22 const index = Math.max(
23 0,
24 props.options.findIndex((option) => option.value === props.selected),
25 );
26 const position = useSharedValue(index);
27 useEffect(() => {
28 position.set(
29 withTiming(index, {
30 duration: 200,
31 easing: Easing.out(Easing.cubic),
32 reduceMotion: ReduceMotion.System,
33 }),
34 );
35 }, [index, position]);
36 const indicatorStyle = useAnimatedStyle(() => ({
37 transform: [
38 {
39 translateX:
40 ((position.value * width) / props.options.length) * (I18nManager.isRTL ? -1 : 1),
41 },
42 ],
43 }));
44 if (Platform.OS === "android") {
45 return <MaterialSegmentedControl {...props} />;
46 }
47 return (
48 <View
49 accessible={false}
50 onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
51 className={cn(
52 "flex-row overflow-hidden",
53 "rounded-full border-continuous bg-card",
54 props.className,
55 )}
56 >
57 <Animated.View
58 pointerEvents="none"
59 className="absolute inset-y-0 rounded-full bg-secondary"
60 style={[{ width: `${100 / props.options.length}%`, start: 0 }, indicatorStyle]}
61 />
62 {props.options.map((option) => {
63 const active = option.value === props.selected;
64 return (
65 <Pressable
66 key={String(option.value)}
67 accessibilityRole={Platform.OS === "ios" ? "button" : (props.role ?? "button")}
68 accessibilityLabel={option.accessibilityLabel ?? option.label}
69 accessibilityState={{ selected: active }}
70 onPress={() => props.onSelect(option.value)}
71 className={cn(
72 "flex-1 items-center justify-center rounded-full",
73 compact ? "h-9" : "h-11",
74 )}
75 >
76 <Text
77 className={cn(
78 compact ? "text-xs" : "text-sm",
79 active ? "font-t3-medium text-secondary-foreground" : "text-foreground-muted",
80 )}
81 >
82 {option.label}
83 </Text>
84 </Pressable>
85 );
86 })}
87 </View>
88 );
89}