108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
import { useEffect, useRef } from "react"
|
|
import { StyleProp, View, ViewStyle, Animated } from "react-native"
|
|
|
|
import { useAppTheme } from "@/theme/context"
|
|
import { $styles } from "@/theme/styles"
|
|
import { s } from "@/utils/responsive"
|
|
|
|
import { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from "./Toggle"
|
|
|
|
export interface RadioToggleProps extends Omit<ToggleProps<RadioInputProps>, "ToggleInput"> {
|
|
/**
|
|
* Optional style prop that affects the dot View.
|
|
*/
|
|
inputDetailStyle?: ViewStyle
|
|
}
|
|
|
|
interface RadioInputProps extends BaseToggleInputProps<RadioToggleProps> {}
|
|
|
|
/**
|
|
* @param {RadioToggleProps} props - The props for the `Radio` component.
|
|
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Radio}
|
|
* @returns {JSX.Element} The rendered `Radio` component.
|
|
*/
|
|
export function Radio(props: RadioToggleProps) {
|
|
return <Toggle accessibilityRole="radio" {...props} ToggleInput={RadioInput} />
|
|
}
|
|
|
|
function RadioInput(props: RadioInputProps) {
|
|
const {
|
|
on,
|
|
status,
|
|
disabled,
|
|
outerStyle: $outerStyleOverride,
|
|
innerStyle: $innerStyleOverride,
|
|
detailStyle: $detailStyleOverride,
|
|
} = props
|
|
|
|
const {
|
|
theme: { colors },
|
|
} = useAppTheme()
|
|
|
|
const opacity = useRef(new Animated.Value(0))
|
|
|
|
useEffect(() => {
|
|
Animated.timing(opacity.current, {
|
|
toValue: on ? 1 : 0,
|
|
duration: 300,
|
|
useNativeDriver: true,
|
|
}).start()
|
|
}, [on])
|
|
|
|
const offBackgroundColor = [
|
|
disabled && colors.palette.neutral400,
|
|
status === "error" && colors.errorBackground,
|
|
colors.palette.neutral200,
|
|
].filter(Boolean)[0]
|
|
|
|
const outerBorderColor = [
|
|
disabled && colors.palette.neutral400,
|
|
status === "error" && colors.error,
|
|
!on && colors.palette.neutral800,
|
|
colors.palette.secondary500,
|
|
].filter(Boolean)[0]
|
|
|
|
const onBackgroundColor = [
|
|
disabled && colors.transparent,
|
|
status === "error" && colors.errorBackground,
|
|
colors.palette.neutral100,
|
|
].filter(Boolean)[0]
|
|
|
|
const dotBackgroundColor = [
|
|
disabled && colors.palette.neutral600,
|
|
status === "error" && colors.error,
|
|
colors.palette.secondary500,
|
|
].filter(Boolean)[0]
|
|
|
|
return (
|
|
<View
|
|
style={[
|
|
$inputOuter,
|
|
{ backgroundColor: offBackgroundColor, borderColor: outerBorderColor },
|
|
$outerStyleOverride,
|
|
]}
|
|
>
|
|
<Animated.View
|
|
style={[
|
|
$styles.toggleInner,
|
|
{ backgroundColor: onBackgroundColor },
|
|
$innerStyleOverride,
|
|
{ opacity: opacity.current },
|
|
]}
|
|
>
|
|
<View
|
|
style={[$radioDetail, { backgroundColor: dotBackgroundColor }, $detailStyleOverride]}
|
|
/>
|
|
</Animated.View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const $radioDetail: ViewStyle = {
|
|
width: s(12),
|
|
height: s(12),
|
|
borderRadius: s(6),
|
|
}
|
|
|
|
const $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: s(12) }]
|