|
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> |
|
); |
|
}; |