Skip to content

Instantly share code, notes, and snippets.

@foyarash
Created May 9, 2022 12:11
Show Gist options
  • Save foyarash/fa260a37fae9b085b89af13f61f46713 to your computer and use it in GitHub Desktop.
Save foyarash/fa260a37fae9b085b89af13f61f46713 to your computer and use it in GitHub Desktop.
import RNPressable, { RNPressableProps } from "@components/atoms/RNPressable";
import Spinner from "@components/atoms/RNSpinner";
import Stack from "@components/atoms/RNStack";
import Text from "@components/atoms/RNText";
import useSpacingProps from "@components/hooks/useSpacingProps";
import { IButtonProps, useContrastText } from "native-base";
import React, { PropsWithChildren, useMemo } from "react";
export interface RNButtonProps extends IButtonProps {}
const sizes = {
lg: {
px: "4",
py: "2",
_text: {
fontSize: "md"
}
},
md: {
px: "3",
py: "2",
_text: {
fontSize: "sm"
}
},
sm: {
px: "2",
py: "2",
_text: {
fontSize: "xs"
}
},
xs: {
px: "2",
py: "2",
_text: {
fontSize: "2xs"
}
}
};
const RNButton = ({
isLoading,
isDisabled,
_text,
_loading,
_disabled,
colorScheme = "primary",
leftIcon,
rightIcon,
_pressed,
children,
variant = "solid",
_spinner,
size = "md",
startIcon,
endIcon,
...props
}: PropsWithChildren<RNButtonProps>) => {
const sizeProps = useMemo(() => {
// @ts-ignore
return sizes[size] ?? { size };
}, [size]);
const spacingProps = useSpacingProps(props);
const variantProps = useMemo<RNPressableProps>(() => {
switch (variant) {
case "outline":
return {
borderWidth: "1",
borderColor: colorScheme === "muted" ? "muted.200" : isDisabled ? "muted.500" : `${colorScheme}.300`,
_text: {
color: isDisabled ? "muted.500" : `${colorScheme}.500`
},
_icon: {
color: `${colorScheme}.600`
},
_pressed: {
bg: `${colorScheme}.600:alpha.50`,
borderColor: `${colorScheme}.700`
},
_spinner: {
size: "sm"
}
};
case "link":
return {
_text: {
color: colorScheme === "muted" ? "muted.800" : isDisabled ? "muted.500" : `${colorScheme}.600`,
textDecorationLine: "underline"
}
};
case "ghost": {
if (colorScheme === "muted") {
return {
_text: {
color: "muted.500"
}
};
}
return {
_text: {
color: isDisabled ? "muted.500" : `${colorScheme}.500`
},
_pressed: {
bg: `${colorScheme}.600:alpha.50`,
borderColor: `${colorScheme}.700`
},
_spinner: {
size: "sm"
}
};
}
case "unstyled":
return {};
case "solid":
default:
return {
bg: `${colorScheme}.500`,
_pressed: {
bg: `${colorScheme}.700`
},
_disabled: {
bg: "trueGray.300"
},
_text: {
color: "white"
},
_loading: {
bg: "warmGray.50"
}
};
}
}, [variant, isDisabled, colorScheme]);
const {
// @ts-ignore
_text: variantText = {},
_pressed: variantPressed,
// @ts-ignore
_spinner: variantSpinner,
// @ts-ignore
_loading: variantLoading,
_disabled: variantDisabled = {},
...variantPropsRest
} = variantProps;
const { _text: sizeText = {}, _icon: sizeIcon, ...sizePropsRest } = sizeProps;
const bg =
!isDisabled && !isLoading
? props.bg ?? props.bgColor ?? variantPropsRest.bg ?? variantPropsRest.bgColor
: isLoading
? _loading?.bg ?? _loading?.bgColor ?? variantLoading?.bg ?? variantLoading?.bgColor
: _disabled?.bg ?? _disabled?.bgColor ?? variantDisabled.bg ?? variantDisabled.bgColor;
const textColor = _text?.color ?? variantText?.color;
const contrastTextColor = useContrastText(bg, textColor);
const allTextProps = useMemo(() => {
const textProp = {
...variantText,
...sizeText,
..._text
};
if (contrastTextColor && !_text?.color) {
textProp.color = contrastTextColor;
}
return textProp;
}, [variantText, sizeText, _text, contrastTextColor]);
if (leftIcon) {
startIcon = leftIcon;
}
if (rightIcon) {
endIcon = rightIcon;
}
if (endIcon && React.isValidElement(endIcon)) {
endIcon = React.Children.map(endIcon, (child: JSX.Element, index: number) => {
return React.cloneElement(child, {
key: `button-end-icon-${index}`,
...allTextProps,
...child.props
});
});
}
if (startIcon && React.isValidElement(startIcon)) {
startIcon = React.Children.map(startIcon, (child: JSX.Element, index: number) => {
return React.cloneElement(child, {
key: `button-start-icon-${index}`,
...allTextProps,
...child.props
});
});
}
return (
<RNPressable
flexDirection="row"
borderRadius="sm"
justifyContent="center"
alignItems="center"
_pressed={{
...variantPressed,
..._pressed
}}
opacity={isDisabled ? 0.5 : isLoading ? 0.8 : 1}
disabled={isDisabled || isLoading}
{...variantPropsRest}
{...sizePropsRest}
{...props}
{...(isDisabled ? variantDisabled : {})}
{...(isDisabled ? _disabled : {})}
{...(isLoading ? variantLoading : {})}
{...(isLoading ? _loading : {})}
{...spacingProps}
>
<Stack direction="row" space="2" alignItems="center">
{!!startIcon && !isLoading && startIcon}
{isLoading && (
<Spinner size="sm" color={allTextProps?.color ?? "coolGray.800"} focusable={false} {..._spinner} />
)}
{typeof children === "string" ? (
<Text fontWeight="medium" {...allTextProps}>
{children}
</Text>
) : (
children
)}
{!!endIcon && !isLoading && endIcon}
</Stack>
</RNPressable>
);
};
const RNPressable = ({ _pressed, ...props }: RNPressableProps) => {
const [style, rest] = useStyledSystemPropsResolver(props);
const [stylePressed] = useStyledSystemPropsResolver(_pressed || {});
return (
<Pressable
{...rest}
style={({ pressed }) => {
if (pressed) {
return {
...style,
...stylePressed
};
}
return style;
}}
/>
);
};
const useSpacingProps = (props: any) => {
const style: any = {};
if (props.px) {
style.pl = props.px;
style.pr = props.px;
}
if (props.py) {
style.pt = props.py;
style.pb = props.py;
}
if (props.mx) {
style.ml = props.mx;
style.mr = props.mx;
}
if (props.my) {
style.mt = props.my;
style.mb = props.my;
}
["pl", "pr", "pt", "pb", "ml", "mr", "mt", "mb"].forEach((attr) => {
if (props[attr]) {
style[attr] = props[attr];
}
});
if (props.p) {
style.pl = props.p;
style.pr = props.p;
style.pt = props.p;
style.pb = props.p;
}
if (props.m) {
style.ml = props.p;
style.mr = props.p;
style.mt = props.p;
style.mb = props.p;
}
return style;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment