import React, { memo, useMemo } from 'react';
import useTheme from '../hooks/useTheme';
import { ThemeName, themeColors } from '../constants/Colors';
import {
Pressable,
Text,
View,
ViewStyle,
PressableProps,
TextProps, ActivityIndicatorProps,
ActivityIndicator
} from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
export type Props = PressableProps & {
themeScheme?: "light" | "dark";
children?: string | React.ReactNode;
textStyle?: TextProps["style"];
textTextProps?: TextProps;
loadingStyle?: ActivityIndicatorProps["style"];
loadingProps?: ActivityIndicatorProps;
size?: "small" | "medium" | "large" | "extraLarge";
icon?: React.ReactNode;
loading?: boolean;
disableMemo?: boolean;
variant?: "default" | "secondary" | "danger" | "warning" | "success" | "outline" | ThemeName;
width?: ViewStyle["width"];
center?: boolean;
style?: PressableProps["style"]
radius?: number;
rippleRadius?: number;
disabled?: boolean;
};
export const PressableButton = memo(function Button({
children = "Button",
style,
themeScheme,
textStyle,
loadingStyle,
loadingProps,
textTextProps,
width,
center = false,
variant = "default",
size = "medium",
icon = undefined,
loading = false,
disabled = false,
radius = 16,
rippleRadius = 1000,
disableMemo = false,
...otherProps
}: Props) {
const { currentTheme, themeScheme: defaultThemeScheme } = useTheme();
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: withTiming(scale.value, { duration: 120 }) }],
}));
const computedWidth =
typeof width === "number" || /^[0-9]+$/.test(width as string)
? Number(width)
: width;
const colorStyle = useMemo(() => {
// all your variant logic
if (variant === 'default') {
return {
backgroundColor: currentTheme.card,
color: currentTheme.foreground,
borderColor: currentTheme.border,
rippleColor: currentTheme.accent,
};
}
if (variant === 'secondary') {
return {
backgroundColor: currentTheme.secondary,
color: currentTheme.secondary_foreground,
borderColor: currentTheme.secondary,
rippleColor: currentTheme.secondary,
};
}
if (variant === 'danger') {
return {
backgroundColor: currentTheme.destructive,
color: currentTheme.destructive_foreground,
borderColor: currentTheme.destructive,
rippleColor: currentTheme.destructive,
};
}
if (variant === 'warning') {
return {
backgroundColor: "hsl(47.9 95.8% 53.1%)",
color: "hsl(26 83.3% 14.1%)",
borderColor: "hsl(47.9 95.8% 53.1%)",
rippleColor: "hsl(47.9 95.8% 53.1%)",
};
}
if (variant === 'success') {
return {
backgroundColor: "hsl(142.1 76.2% 36.3%)",
color: "hsl(355.7 100% 97.3%)",
borderColor: "hsl(142.1 76.2% 36.3%)",
rippleColor: "hsl(142.1 76.2% 36.3%)",
};
}
if (variant === 'outline') {
return {
backgroundColor: currentTheme.card,
color: currentTheme.foreground,
borderColor: currentTheme.border,
rippleColor: currentTheme.accent,
};
}
const theme = themeColors.find((t) => t.name === variant)?.[
(themeScheme ?? defaultThemeScheme) as "light" | "dark"
];
return {
backgroundColor: theme?.primary || currentTheme.primary,
color: theme?.primary_foreground || currentTheme.primary_foreground,
borderColor: theme?.border || currentTheme.border,
rippleColor: theme?.muted || currentTheme.muted,
};
}, [currentTheme, themeScheme, defaultThemeScheme, variant]);
const sizeVariant = useMemo(() => {
switch (size) {
case "small":
return {
paddingVertical: 5,
paddingHorizontal: 10,
borderRadius: 5,
fontSize: 12,
};
case "large":
return {
paddingVertical: 15,
paddingHorizontal: 20,
borderRadius: 15,
fontSize: 16,
};
case "extraLarge":
return {
paddingVertical: 20,
paddingHorizontal: 25,
borderRadius: 20,
fontSize: 18,
};
default:
return {
paddingVertical: 10,
paddingHorizontal: 15,
borderRadius: 10,
fontSize: 14,
};
}
}, [size]);
if (!currentTheme) return <>>;
return (
{
if (!disabled) scale.value = 0.96;
}}
onPressOut={() => {
if (!disabled) scale.value = 1;
}}
android_ripple={{
color: colorStyle.rippleColor,
radius: rippleRadius,
borderless: true,
}}
disabled={disabled}
style={({ pressed }) => [
{
borderRadius: radius,
borderWidth: 0.6,
borderColor: colorStyle.borderColor,
backgroundColor: colorStyle.backgroundColor,
alignItems: "center",
justifyContent: "center",
},
computedWidth,
style as any,
pressed ? { opacity: 0.85 } : undefined,
disabled ? { opacity: 0.5 } : undefined,
]}
{...otherProps}
>
);
});
export default PressableButton;
const ButtonContent = ({
children,
textStyle,
icon,
loading,
loadingStyle,
loadingProps,
textProps,
color
}: {
children: string | React.ReactNode,
icon: React.ReactNode,
loading: boolean,
textProps?: TextProps,
textStyle: TextProps["style"],
loadingProps?: ActivityIndicatorProps,
loadingStyle: ActivityIndicatorProps["style"],
color: string,
}) => {
if (typeof children === "string") {
return <>
{icon ? icon : <>>}
{children}
>
}
else if (typeof children === "object") {
return React.Children.map(children, (child, index) => {
if (typeof child === "string") {
return (
{child}
);
} else {
return child;
}
});
};
return (
<>
{children}
>
)
};
export const PressableView = memo(function Button({
children = "Button",
style,
themeScheme,
textStyle,
loadingStyle,
loadingProps,
textTextProps,
width,
center = false,
variant = "default",
size = "medium",
icon = undefined,
loading = false,
disabled = false,
disableMemo = false,
radius = 16,
rippleRadius = 1000,
...otherProps
}: Props) {
const { currentTheme } = useTheme();
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: withTiming(scale.value, { duration: 120 }) }],
};
});
const computedWidth =
typeof width === "number" || /^[0-9]+$/.test(width as string)
? Number(width)
: width;
if (!currentTheme) return <>>;
return (
{
if (!disabled) scale.value = 0.96;
}}
onPressOut={() => {
if (!disabled) scale.value = 1;
}}
android_ripple={{
color: currentTheme.muted,
radius: rippleRadius,
borderless: true,
}}
disabled={disabled}
style={({ }) => [
{
borderRadius: radius,
borderWidth: 0.6,
borderColor: currentTheme.border,
backgroundColor: currentTheme.card,
alignItems: "center",
justifyContent: "center",
},
computedWidth,
style as any,
disabled ? { opacity: 0.5 } : undefined,
]}
{...otherProps}
>
{children}
);
});