All files / packages/ui/src/components/Switch index.tsx

100% Statements 11/11
100% Branches 13/13
100% Functions 2/2
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105                                                      2x                     12x 12x 12x 12x   12x         12x     12x   12x                                                           12x                                          
import {
  Switch as RNSwitch,
  Text,
  TextStyle,
  View,
  ViewStyle,
} from "react-native";
 
import { makeStyles } from "@repo/ui/themes/makeStyles";
import { useTheme } from "@repo/ui/themes/ThemeContext";
 
export interface CustomSwitchProps {
  value: boolean;
  onValueChange: (value: boolean) => void;
  label?: string;
  disabled?: boolean;
  disableLabelStyle?: boolean;
  containerStyle?: ViewStyle;
  labelStyle?: TextStyle;
  errorMessage?: string;
  testID?: string;
  switchProps?: Omit<
    React.ComponentProps<typeof RNSwitch>,
    "value" | "onValueChange" | "disabled"
  >;
}
 
export const Switch = ({
  value,
  onValueChange,
  label,
  disabled = false,
  containerStyle,
  labelStyle,
  switchProps,
  testID,
  errorMessage,
}: CustomSwitchProps) => {
  const { theme } = useTheme();
  const styles = useStyles();
  const resolvedValue = Boolean(value);
  const switchRenderKey = resolvedValue ? "switch-on" : "switch-off";
 
  const defaultTrackColor = {
    false: theme.colors.gray50,
    true: theme.colors.blue80,
  };
 
  const defaultThumbColor = resolvedValue
    ? theme.colors.white
    : theme.colors.gray10;
  const defaultIosBackgroundColor = theme.colors.gray50;
 
  return (
    <View style={[styles.container, containerStyle]}>
      {label ? <Text style={[styles.label, labelStyle]}>{label}</Text> : null}
 
      <RNSwitch
        key={switchRenderKey}
        {...switchProps}
        testID={testID}
        trackColor={{
          ...defaultTrackColor,
          ...switchProps?.trackColor,
        }}
        thumbColor={switchProps?.thumbColor ?? defaultThumbColor}
        ios_backgroundColor={
          switchProps?.ios_backgroundColor ?? defaultIosBackgroundColor
        }
        value={resolvedValue}
        onValueChange={onValueChange}
        disabled={disabled}
        accessibilityState={{ checked: resolvedValue, disabled }}
      />
      {errorMessage && (
        <Text style={styles.error} accessibilityLiveRegion="polite">
          {errorMessage}
        </Text>
      )}
    </View>
  );
};
 
const useStyles = makeStyles((theme) => ({
  container: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    gap: theme.metrics.spacing[2],
    marginVertical: theme.metrics.spacing[1],
  },
  label: {
    fontSize: theme.metrics.textSize[16],
    color: theme.colors.text.info,
    flex: 1,
    flexShrink: 1,
  },
  error: {
    position: "absolute",
    bottom: -theme.metrics.spacing[4.5],
    fontSize: theme.metrics.textSize[12],
    color: theme.colors.text.error,
  },
}));