Files
sgw-owner-app/components/IconButton.tsx
2025-11-28 16:59:57 +07:00

160 lines
4.1 KiB
TypeScript

import React from "react";
import {
ActivityIndicator,
GestureResponderEvent,
StyleProp,
StyleSheet,
Text,
TouchableOpacity,
View,
ViewStyle,
} from "react-native";
type ButtonType = "primary" | "default" | "dashed" | "text" | "link" | "danger";
type ButtonShape = "default" | "circle" | "round";
type ButtonSize = "small" | "middle" | "large";
export interface IconButtonProps {
type?: ButtonType;
shape?: ButtonShape;
size?: ButtonSize;
icon?: React.ReactNode; // render an icon component, e.g. <AntDesign name="plus" />
loading?: boolean;
disabled?: boolean;
onPress?: (e?: GestureResponderEvent) => void;
children?: React.ReactNode; // label text
style?: StyleProp<ViewStyle>;
block?: boolean; // full width
activeOpacity?: number;
}
/**
* IconButton
* A lightweight Button component inspired by Ant Design Button API, tuned for React Native.
* Accepts an `icon` prop as a React node for maximum flexibility.
*/
const IconButton: React.FC<IconButtonProps> = ({
type = "default",
shape = "default",
size = "middle",
icon,
loading = false,
disabled = false,
onPress,
children,
style,
block = false,
activeOpacity = 0.8,
}) => {
const sizeMap = {
small: { height: 32, fontSize: 14, paddingHorizontal: 10 },
middle: { height: 40, fontSize: 16, paddingHorizontal: 14 },
large: { height: 48, fontSize: 18, paddingHorizontal: 18 },
} as const;
const colors: Record<
ButtonType,
{ backgroundColor?: string; textColor: string; borderColor?: string }
> = {
primary: { backgroundColor: "#4ecdc4", textColor: "#fff" },
default: {
backgroundColor: "#f2f2f2",
textColor: "#111",
borderColor: "#e6e6e6",
},
dashed: {
backgroundColor: "#fff",
textColor: "#111",
borderColor: "#d9d9d9",
},
text: { backgroundColor: "transparent", textColor: "#111" },
link: { backgroundColor: "transparent", textColor: "#4ecdc4" },
danger: { backgroundColor: "#e74c3c", textColor: "#fff" },
};
const sz = sizeMap[size];
const color = colors[type];
const isCircle = shape === "circle";
const isRound = shape === "round";
const handlePress = (e: GestureResponderEvent) => {
if (disabled || loading) return;
onPress?.(e);
};
return (
<TouchableOpacity
activeOpacity={activeOpacity}
onPress={handlePress}
disabled={disabled || loading}
style={[
styles.button,
{
height: sz.height,
paddingHorizontal: isCircle ? 0 : sz.paddingHorizontal,
backgroundColor: color.backgroundColor ?? "transparent",
borderColor: color.borderColor ?? "transparent",
borderWidth: type === "dashed" ? 1 : color.borderColor ? 1 : 0,
width: isCircle ? sz.height : block ? "100%" : undefined,
borderRadius: isCircle ? sz.height / 2 : isRound ? 999 : 8,
opacity: disabled ? 0.6 : 1,
},
type === "dashed" ? { borderStyle: "dashed" } : null,
style,
]}
>
<View style={styles.content}>
{loading ? (
<ActivityIndicator
size={"small"}
color={color.textColor}
style={styles.iconContainer}
/>
) : icon ? (
<View style={styles.iconContainer}>{icon}</View>
) : null}
{children ? (
<Text
numberOfLines={1}
style={[
styles.text,
{
color: color.textColor,
fontSize: sz.fontSize,
marginLeft: icon || loading ? 6 : 0,
},
]}
>
{children}
</Text>
) : null}
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
borderRadius: 8,
borderColor: "transparent",
},
content: {
flexDirection: "row",
alignItems: "center",
},
iconContainer: {
alignItems: "center",
justifyContent: "center",
},
text: {
fontWeight: "600",
},
});
export default IconButton;