Files

107 lines
3.0 KiB
TypeScript
Raw Permalink Normal View History

2026-02-05 13:16:05 +08:00
import { useEffect, useRef, useCallback } from "react"
import { Animated, StyleProp, View, ViewStyle } from "react-native"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { s } from "@/utils/responsive"
import { Icon, IconTypes } from "../Icon"
import { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from "./Toggle"
export interface CheckboxToggleProps extends Omit<ToggleProps<CheckboxInputProps>, "ToggleInput"> {
/**
* Checkbox-only prop that changes the icon used for the "on" state.
*/
icon?: IconTypes
}
interface CheckboxInputProps extends BaseToggleInputProps<CheckboxToggleProps> {
icon?: CheckboxToggleProps["icon"]
}
/**
* @param {CheckboxToggleProps} props - The props for the `Checkbox` component.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Checkbox}
* @returns {JSX.Element} The rendered `Checkbox` component.
*/
export function Checkbox(props: CheckboxToggleProps) {
const { icon, ...rest } = props
const checkboxInput = useCallback(
(toggleProps: CheckboxInputProps) => <CheckboxInput {...toggleProps} icon={icon} />,
[icon],
)
return <Toggle accessibilityRole="checkbox" {...rest} ToggleInput={checkboxInput} />
}
function CheckboxInput(props: CheckboxInputProps) {
const {
on,
status,
disabled,
icon = "check",
outerStyle: $outerStyleOverride,
innerStyle: $innerStyleOverride,
} = 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.secondary500,
].filter(Boolean)[0]
const iconTintColor = [
disabled && colors.palette.neutral600,
status === "error" && colors.error,
colors.palette.accent100,
].filter(Boolean)[0] as string | undefined
return (
<View
style={[
$inputOuter,
{ backgroundColor: offBackgroundColor, borderColor: outerBorderColor },
$outerStyleOverride,
]}
>
<Animated.View
style={[
$styles.toggleInner,
{ backgroundColor: onBackgroundColor },
$innerStyleOverride,
{ opacity: opacity.current },
]}
>
<Icon icon={icon} size={s(20)} color={iconTintColor} />
</Animated.View>
</View>
)
}
const $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: s(4) }]