import { useCallback, useMemo, type ReactNode } from "react"; import { Pressable, Text, View, type PressableStateCallbackType } from "react-native"; import type { StyleProp, TextStyle, ViewStyle } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; type SegmentedControlSize = "sm" | "md"; type SegmentedControlIconRenderer = (props: { color: string; size: number }) => ReactNode; export interface SegmentedControlOption { value: T; label: string; icon?: SegmentedControlIconRenderer; disabled?: boolean; testID?: string; } interface SegmentedControlProps { options: SegmentedControlOption[]; value: T; onValueChange: (value: T) => void; size?: SegmentedControlSize; hideLabels?: boolean; style?: StyleProp; testID?: string; } export function SegmentedControl({ options, value, onValueChange, size = "md", hideLabels = false, style, testID, }: SegmentedControlProps) { const { theme } = useUnistyles(); const containerSizeStyle = size === "sm" ? styles.containerSm : styles.containerMd; const segmentSizeStyle = size === "sm" ? styles.segmentSm : styles.segmentMd; const labelSizeStyle = size === "sm" ? styles.labelSm : styles.labelMd; const iconSize = size === "sm" ? theme.iconSize.sm : theme.iconSize.md; const containerStyle = useMemo( () => [styles.container, containerSizeStyle, style], [containerSizeStyle, style], ); return ( {options.map((option) => { const isSelected = option.value === value; const iconColor = isSelected ? theme.colors.foreground : theme.colors.foregroundMuted; return ( ); })} ); } function SegmentItem({ option, isSelected, iconColor, iconSize, hideLabels, segmentSizeStyle, labelSizeStyle, currentValue, onValueChange, }: { option: SegmentedControlOption; isSelected: boolean; iconColor: string; iconSize: number; hideLabels: boolean; segmentSizeStyle: StyleProp; labelSizeStyle: StyleProp; currentValue: T; onValueChange: (value: T) => void; }) { const labelStyle = useMemo( () => [styles.label, labelSizeStyle, isSelected && styles.labelSelected], [labelSizeStyle, isSelected], ); const handlePress = useCallback(() => { if (!option.disabled && option.value !== currentValue) { onValueChange(option.value); } }, [option.disabled, option.value, currentValue, onValueChange]); const pressableStyle = useCallback( ({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [ styles.segment, segmentSizeStyle, isSelected && styles.segmentSelected, Boolean(hovered) && !isSelected && styles.segmentHover, pressed && !isSelected && styles.segmentPressed, option.disabled && styles.segmentDisabled, ], [isSelected, option.disabled, segmentSizeStyle], ); const accessibilityState = useMemo( () => ({ selected: isSelected, disabled: option.disabled }), [isSelected, option.disabled], ); return ( {option.icon ? ( {option.icon({ color: iconColor, size: iconSize })} ) : null} {hideLabels ? null : ( {option.label} )} ); } const styles = StyleSheet.create((theme) => ({ container: { flexDirection: "row", alignItems: "stretch", maxWidth: "100%", backgroundColor: theme.colors.surface2, borderRadius: theme.borderRadius.lg, }, containerSm: { padding: 2, }, containerMd: { padding: 3, }, segment: { flexDirection: "row", alignItems: "center", justifyContent: "center", flexShrink: 0, borderRadius: theme.borderRadius.lg, gap: theme.spacing[1], }, segmentSm: { paddingVertical: theme.spacing[2], paddingHorizontal: theme.spacing[4], }, segmentMd: { paddingVertical: theme.spacing[3], paddingHorizontal: theme.spacing[6], }, segmentSelected: { backgroundColor: theme.colors.surface0, shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.08, shadowRadius: 2, elevation: 1, }, segmentHover: { backgroundColor: theme.colors.surface1, }, segmentPressed: { backgroundColor: theme.colors.surface1, }, segmentDisabled: { opacity: theme.opacity[50], }, iconContainer: { alignItems: "center", justifyContent: "center", }, label: { color: theme.colors.foregroundMuted, fontWeight: theme.fontWeight.normal, }, labelSm: { fontSize: theme.fontSize.sm, }, labelMd: { fontSize: theme.fontSize.base, }, labelSelected: { color: theme.colors.foreground, }, }));