Skip to content

Instantly share code, notes, and snippets.

@cesswhite
Last active July 24, 2025 16:06
Show Gist options
  • Save cesswhite/2532945c8ec266f851f97e3d9f086ff9 to your computer and use it in GitHub Desktop.
Save cesswhite/2532945c8ec266f851f97e3d9f086ff9 to your computer and use it in GitHub Desktop.
Components by Code With Beto Team to create React Native Apps
import { useColorScheme } from "@/hooks/useColorScheme";
import {
ColorConfig,
getColorValue,
RADIUS_VALUES,
UIColor,
UIRadius,
UISize,
} from "@/types/ui";
import * as Haptics from "expo-haptics";
import { SFSymbol } from "expo-symbols";
import React, { useEffect, useMemo, useRef } from "react";
import { Animated, Pressable, StyleSheet, ViewStyle } from "react-native";
import { Icon } from "./Icon";
import { Text } from "./Text";
type ButtonVariant = "solid" | "outline" | "soft" | "subtle" | "link";
const generateVariantConfig = (
color: UIColor,
colorScheme: "light" | "dark"
): Record<ButtonVariant, ColorConfig> => {
const isDark = colorScheme === "dark";
if (color === "black") {
const bgColor = getColorValue("black", 50);
const textColor = getColorValue("black", 950);
const borderColor = bgColor;
return {
solid: {
backgroundColor: bgColor,
borderColor: borderColor,
textColor: textColor,
borderWidth: 1,
},
outline: {
backgroundColor: "transparent",
borderColor: borderColor,
textColor: bgColor,
borderWidth: 1,
},
soft: {
backgroundColor: `${bgColor}${isDark ? "20" : "10"}`,
borderColor: "transparent",
textColor: bgColor,
borderWidth: 0,
},
subtle: {
backgroundColor: `${bgColor}${isDark ? "20" : "10"}`,
borderColor: borderColor,
textColor: bgColor,
borderWidth: 1,
},
link: {
backgroundColor: "transparent",
borderColor: "transparent",
textColor: bgColor,
borderWidth: 0,
},
};
}
if (color === "white") {
const bgColor = getColorValue("white", 950); // Full white
const textColor = getColorValue("white", 50); // Full black
const borderColor = bgColor;
return {
solid: {
backgroundColor: bgColor,
borderColor: borderColor,
textColor: textColor,
borderWidth: 1,
},
outline: {
backgroundColor: "transparent",
borderColor: borderColor,
textColor: bgColor,
borderWidth: 1,
},
soft: {
backgroundColor: `${bgColor}${isDark ? "20" : "10"}`,
borderColor: "transparent",
textColor: bgColor,
borderWidth: 0,
},
subtle: {
backgroundColor: `${bgColor}${isDark ? "20" : "10"}`,
borderColor: borderColor,
textColor: bgColor,
borderWidth: 1,
},
link: {
backgroundColor: "transparent",
borderColor: "transparent",
textColor: bgColor,
borderWidth: 0,
},
};
}
return {
solid: {
backgroundColor: getColorValue(color, isDark ? 500 : 600),
borderColor: getColorValue(color, isDark ? 500 : 600),
textColor: getColorValue(color, isDark ? 950 : 50),
borderWidth: 1,
},
outline: {
backgroundColor: "transparent",
borderColor: getColorValue(color, isDark ? 500 : 600),
textColor: getColorValue(color, isDark ? 500 : 600),
borderWidth: 1,
},
soft: {
backgroundColor: `${getColorValue(color, isDark ? 500 : 600)}${
isDark ? "20" : "10"
}`,
borderColor: "transparent",
textColor: getColorValue(color, isDark ? 500 : 600),
borderWidth: 0,
},
subtle: {
backgroundColor: `${getColorValue(color, isDark ? 500 : 600)}${
isDark ? "20" : "10"
}`,
borderColor: getColorValue(color, isDark ? 500 : 600),
textColor: getColorValue(color, isDark ? 500 : 600),
borderWidth: 1,
},
link: {
backgroundColor: "transparent",
borderColor: "transparent",
textColor: getColorValue(color, isDark ? 500 : 600),
borderWidth: 0,
},
};
};
interface ButtonProps {
title?: string;
onPress: () => void;
disabled?: boolean;
loading?: boolean;
selected?: boolean;
variant?: ButtonVariant;
color?: UIColor;
size?: UISize;
radius?: UIRadius;
style?: ViewStyle;
symbol?: string;
haptic?: boolean;
hapticStyle?: "light" | "medium" | "heavy";
}
export function Button({
title,
onPress,
disabled = false,
loading = false,
selected = false,
variant = "outline",
color = "blue",
size = "md",
radius = "md",
style,
symbol,
haptic = false,
hapticStyle = "light",
}: ButtonProps) {
const colorScheme = useColorScheme();
const spinValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (loading) {
const spin = () => {
spinValue.setValue(0);
Animated.timing(spinValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start(() => spin());
};
spin();
} else {
spinValue.stopAnimation();
}
}, [loading, spinValue]);
const spinInterpolate = spinValue.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
});
const variantConfig = useMemo(() => {
const variants = generateVariantConfig(color, colorScheme ?? "light");
return variants[variant];
}, [color, colorScheme, variant]);
const isDisabled = disabled || loading;
const buttonStyles = useMemo(() => {
const baseStyles: ViewStyle = {
...styles.button,
...SIZE_STYLES[size],
backgroundColor: variantConfig.backgroundColor,
borderColor: variantConfig.borderColor,
borderWidth: variantConfig.borderWidth,
borderRadius: RADIUS_VALUES[radius],
};
return [baseStyles, style];
}, [size, variantConfig, style, radius]);
const textStyles = useMemo(() => {
return [
styles.buttonText,
TEXT_SIZE_STYLES[size],
{ color: variantConfig.textColor },
];
}, [size, variantConfig]);
const iconColor = variantConfig.textColor;
const displayIcon = loading ? "arrow.2.circlepath" : symbol;
const shouldCenterIcon = !title && displayIcon;
const handlePress = () => {
if (!isDisabled) {
// Haptic feedback
if (haptic) {
const hapticStyleMap = {
light: Haptics.ImpactFeedbackStyle.Light,
medium: Haptics.ImpactFeedbackStyle.Medium,
heavy: Haptics.ImpactFeedbackStyle.Heavy,
};
Haptics.impactAsync(hapticStyleMap[hapticStyle]);
}
onPress();
}
};
return (
<Pressable
style={({ pressed }) => [
...buttonStyles,
shouldCenterIcon && styles.iconOnly,
isDisabled && styles.disabled,
pressed && !isDisabled && styles.pressed,
]}
onPress={isDisabled ? undefined : handlePress}
disabled={isDisabled}
>
{displayIcon &&
(loading ? (
<Animated.View style={{ transform: [{ rotate: spinInterpolate }] }}>
<Icon
symbol={displayIcon as SFSymbol}
size={size}
color={iconColor}
/>
</Animated.View>
) : (
<Icon
symbol={displayIcon as SFSymbol}
size={size}
color={iconColor}
/>
))}
{title && <Text style={textStyles}>{title}</Text>}
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 5,
width: "100%",
},
iconOnly: {
justifyContent: "center",
gap: 0,
},
xs: { height: 28 },
sm: { height: 36 },
md: { height: 48 },
lg: { height: 56 },
xl: { height: 64 },
xxl: { height: 72 },
buttonText: { fontWeight: "600" },
xsText: { fontSize: 12 },
smText: { fontSize: 14 },
mdText: { fontSize: 16 },
lgText: { fontSize: 18 },
xlText: { fontSize: 20 },
xxlText: { fontSize: 22 },
disabled: { opacity: 0.5 },
pressed: { opacity: 0.8, transform: [{ scale: 0.98 }] },
});
const SIZE_STYLES = {
xs: styles.xs,
sm: styles.sm,
md: styles.md,
lg: styles.lg,
xl: styles.xl,
"2xl": styles.xxl,
} as const;
const TEXT_SIZE_STYLES = {
xs: styles.xsText,
sm: styles.smText,
md: styles.mdText,
lg: styles.lgText,
xl: styles.xlText,
"2xl": styles.xxlText,
} as const;
import { UISize } from "@/types/ui";
import { SFSymbol, SymbolView } from "expo-symbols";
import React from "react";
import { StyleSheet, ViewStyle } from "react-native";
interface IconProps {
symbol: SFSymbol;
size?: UISize | number;
color?: string;
style?: ViewStyle;
type?: "monochrome" | "hierarchical" | "palette" | "multicolor";
}
const sizeMap: Record<UISize, number> = {
xs: 14,
sm: 18,
md: 22,
lg: 26,
xl: 30,
"2xl": 34,
};
export const Icon: React.FC<IconProps> = ({
symbol,
size = "md",
color,
style,
type = "hierarchical",
}) => {
const iconSize = typeof size === "string" ? sizeMap[size] : size;
return (
<SymbolView
name={symbol}
style={[styles.symbol, { width: iconSize, height: iconSize }, style]}
tintColor={color}
type={type}
resizeMode="scaleAspectFit"
/>
);
};
const styles = StyleSheet.create({
symbol: {
width: 24,
height: 24,
},
});
// Color.ts
export const Color = {
grayscale: {
50: "#000000",
100: "#1A1A1A",
200: "#333333",
300: "#4D4D4D",
400: "#666666",
500: "#808080",
600: "#999999",
700: "#B3B3B3",
800: "#CCCCCC",
900: "#E6E6E6",
950: "#FFFFFF",
},
slate: {
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1e293b",
900: "#0f172a",
950: "#020617",
},
gray: {
50: "#f9fafb",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
400: "#9ca3af",
500: "#6b7280",
600: "#4b5563",
700: "#374151",
800: "#1f2937",
900: "#111827",
950: "#030712",
},
zinc: {
50: "#fafafa",
100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a",
600: "#52525b",
700: "#3f3f46",
800: "#27272a",
900: "#18181b",
950: "#09090b",
},
neutral: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e5e5e5",
300: "#d4d4d4",
400: "#a3a3a3",
500: "#737373",
600: "#525252",
700: "#404040",
800: "#262626",
900: "#171717",
950: "#0a0a0a",
},
stone: {
50: "#fafaf9",
100: "#f5f5f4",
200: "#e7e5e4",
300: "#d6d3d1",
400: "#a8a29e",
500: "#78716c",
600: "#57534e",
700: "#44403c",
800: "#292524",
900: "#1c1917",
950: "#0c0a09",
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a",
},
orange: {
50: "#fff7ed",
100: "#ffedd5",
200: "#fed7aa",
300: "#fdba74",
400: "#fb923c",
500: "#f97316",
600: "#ea580c",
700: "#c2410c",
800: "#9a3412",
900: "#7c2d12",
950: "#431407",
},
amber: {
50: "#fffbeb",
100: "#fef3c7",
200: "#fde68a",
300: "#fcd34d",
400: "#fbbf24",
500: "#f59e0b",
600: "#d97706",
700: "#b45309",
800: "#92400e",
900: "#78350f",
950: "#451a03",
},
yellow: {
50: "#fefce8",
100: "#fef9c3",
200: "#fef08a",
300: "#fde047",
400: "#facc15",
500: "#eab308",
600: "#ca8a04",
700: "#a16207",
800: "#854d0e",
900: "#713f12",
950: "#422006",
},
lime: {
50: "#f7fee7",
100: "#ecfccb",
200: "#d9f99d",
300: "#bef264",
400: "#a3e635",
500: "#84cc16",
600: "#65a30d",
700: "#4d7c0f",
800: "#3f6212",
900: "#365314",
950: "#1a2e05",
},
green: {
50: "#f0fdf4",
100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac",
400: "#4ade80",
500: "#22c55e",
600: "#16a34a",
700: "#15803d",
800: "#166534",
900: "#14532d",
950: "#052e16",
},
emerald: {
50: "#ecfdf5",
100: "#d1fae5",
200: "#a7f3d0",
300: "#6ee7b7",
400: "#34d399",
500: "#10b981",
600: "#059669",
700: "#047857",
800: "#065f46",
900: "#064e3b",
950: "#022c22",
},
teal: {
50: "#f0fdfa",
100: "#ccfbf1",
200: "#99f6e4",
300: "#5eead4",
400: "#2dd4bf",
500: "#14b8a6",
600: "#0d9488",
700: "#0f766e",
800: "#115e59",
900: "#134e4a",
950: "#042f2e",
},
cyan: {
50: "#ecfeff",
100: "#cffafe",
200: "#a5f3fc",
300: "#67e8f9",
400: "#22d3ee",
500: "#06b6d4",
600: "#0891b2",
700: "#0e7490",
800: "#155e75",
900: "#164e63",
950: "#083344",
},
sky: {
50: "#f0f9ff",
100: "#e0f2fe",
200: "#bae6fd",
300: "#7dd3fc",
400: "#38bdf8",
500: "#0ea5e9",
600: "#0284c7",
700: "#0369a1",
800: "#075985",
900: "#0c4a6e",
950: "#082f49",
},
blue: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
950: "#172554",
},
indigo: {
50: "#eef2ff",
100: "#e0e7ff",
200: "#c7d2fe",
300: "#a5b4fc",
400: "#818cf8",
500: "#6366f1",
600: "#4f46e5",
700: "#4338ca",
800: "#3730a3",
900: "#312e81",
950: "#1e1b4b",
},
violet: {
50: "#f5f3ff",
100: "#ede9fe",
200: "#ddd6fe",
300: "#c4b5fd",
400: "#a78bfa",
500: "#8b5cf6",
600: "#7c3aed",
700: "#6d28d9",
800: "#5b21b6",
900: "#4c1d95",
950: "#2e1065",
},
purple: {
50: "#faf5ff",
100: "#f3e8ff",
200: "#e9d5ff",
300: "#d8b4fe",
400: "#c084fc",
500: "#a855f7",
600: "#9333ea",
700: "#7e22ce",
800: "#6b21a8",
900: "#581c87",
950: "#3b0764",
},
fuchsia: {
50: "#fdf4ff",
100: "#fae8ff",
200: "#f5d0fe",
300: "#f0abfc",
400: "#e879f9",
500: "#d946ef",
600: "#c026d3",
700: "#a21caf",
800: "#86198f",
900: "#701a75",
950: "#4a044e",
},
pink: {
50: "#fdf2f8",
100: "#fce7f3",
200: "#fbcfe8",
300: "#f9a8d4",
400: "#f472b6",
500: "#ec4899",
600: "#db2777",
700: "#be185d",
800: "#9d174d",
900: "#831843",
950: "#500724",
},
rose: {
50: "#fff1f2",
100: "#ffe4e6",
200: "#fecdd3",
300: "#fda4af",
400: "#fb7185",
500: "#f43f5e",
600: "#e11d48",
700: "#be123c",
800: "#9f1239",
900: "#881337",
950: "#4c0519",
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment