SNYTH
Mobile

Button

Explore the Snyth registry of premium, high-performance UI blocks.

Preview

Installation

1. Install Dependencies

This component requires React Native Reanimated. Please follow the official installation guide to set up the library in your React Native or Expo project first.

Then, install the required animation peer:

npm install react-native-reanimated
yarn add react-native-reanimated
pnpm add react-native-reanimated
bun add react-native-reanimated

2. Add Component

Using CLI

Use the Snyth CLI to automatically add the button component to your project.

npx snyth add button
bunx snyth add button

Manual Installation

Create the file at components/file-path/button.tsx.

import React, { useCallback } from "react";
import {
  StyleSheet,
  useColorScheme,
  Pressable,
  Dimensions,
  Platform,
} from "react-native";
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  runOnJS,
  interpolate,
  interpolateColor,
} from "react-native-reanimated";

const { width: SCREEN_WIDTH } = Dimensions.get("window");

const THEME = {
  black: "hsl(0, 0%, 0%)",
  white: "hsl(0, 0%, 100%)",
};

interface ButtonProps {
  label: string;
  onAction?: () => void;
  size?: "small" | "medium" | "large";
}

const Button: React.FC<ButtonProps> = ({
  label,
  onAction,
  size = "medium",
}) => {
  const isDark = useColorScheme() === "dark";

  const mainColor = isDark ? THEME.white : THEME.black;
  const inverseColor = isDark ? THEME.black : THEME.white;

  const interaction = useSharedValue(0);

  const SIZES = {
    small: { height: 40, width: 120, fontSize: 12 },
    medium: { height: 50, width: 200, fontSize: 14 },
    large: { height: 60, width: SCREEN_WIDTH * 0.85, fontSize: 16 },
  };

  const config = SIZES[size];

  const handleAction = useCallback(() => {
    console.log("Action Triggered");
    if (onAction) onAction();
  }, [onAction]);

  const handlePressIn = () => {
    interaction.value = withTiming(1, { duration: 200 });
  };

  const handlePressOut = () => {
    if (interaction.value >= 0.9) {
      runOnJS(handleAction)();
    }
    interaction.value = withTiming(0, { duration: 200 });
  };

  const rButtonStyle = useAnimatedStyle(() => {
    return {
      backgroundColor: interpolateColor(
        interaction.value,
        [0, 1],
        ["transparent", mainColor],
      ),
      borderColor: mainColor,
      borderRadius: interpolate(
        interaction.value,
        [0, 1],
        [4, config.height / 2],
      ),
      borderWidth: 2,
    };
  });

  const rTextStyle = useAnimatedStyle(() => {
    return {
      color: interpolateColor(
        interaction.value,
        [0, 1],
        [mainColor, inverseColor],
      ),
    };
  });

  return (
    <Animated.View
      style={[
        styles.container,
        {
          height: config.height,
          width: config.width,
        },
        rButtonStyle,
      ]}
    >
      <Pressable
        onPressIn={handlePressIn}
        onPressOut={handlePressOut}
        style={styles.pressable}
      >
        <Animated.Text
          style={[styles.label, { fontSize: config.fontSize }, rTextStyle]}
        >
          {label}
        </Animated.Text>
      </Pressable>
    </Animated.View>
  );
};

const styles = StyleSheet.create({
  container: {
    alignSelf: "center",
    marginVertical: 10,
    overflow: "hidden",
  },
  pressable: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  label: {
    fontWeight: "900",
    textTransform: "uppercase",
    letterSpacing: 1.5,
    fontFamily:
      Platform.OS === "ios"
        ? "HelveticaNeue-CondensedBold"
        : "sans-serif-condensed",
  },
});

export default Button;

Usage

import Button from "@/components/file-path/button";

export default function Example() {
  return (
    <div className="flex gap-4">
      <Button label="Press me" />

    </div>
  );
}