template_0205
This commit is contained in:
286
RN_TEMPLATE/app/components/Toggle/Toggle.tsx
Normal file
286
RN_TEMPLATE/app/components/Toggle/Toggle.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
import { ComponentType, FC, useMemo } from "react"
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
ImageStyle,
|
||||
StyleProp,
|
||||
SwitchProps,
|
||||
TextInputProps,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
TouchableOpacityProps,
|
||||
View,
|
||||
ViewProps,
|
||||
ViewStyle,
|
||||
} from "react-native"
|
||||
|
||||
import { useAppTheme } from "@/theme/context"
|
||||
import { $styles } from "@/theme/styles"
|
||||
import type { ThemedStyle } from "@/theme/types"
|
||||
import { s } from "@/utils/responsive"
|
||||
|
||||
import { Text, TextProps } from "../Text"
|
||||
|
||||
export interface ToggleProps<T> extends Omit<TouchableOpacityProps, "style"> {
|
||||
/**
|
||||
* A style modifier for different input states.
|
||||
*/
|
||||
status?: "error" | "disabled"
|
||||
/**
|
||||
* If false, input is not editable. The default value is true.
|
||||
*/
|
||||
editable?: TextInputProps["editable"]
|
||||
/**
|
||||
* The value of the field. If true the component will be turned on.
|
||||
*/
|
||||
value?: boolean
|
||||
/**
|
||||
* Invoked with the new value when the value changes.
|
||||
*/
|
||||
onValueChange?: SwitchProps["onValueChange"]
|
||||
/**
|
||||
* Style overrides for the container
|
||||
*/
|
||||
containerStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Style overrides for the input wrapper
|
||||
*/
|
||||
inputWrapperStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Optional input wrapper style override.
|
||||
* This gives the inputs their size, shape, "off" background-color, and outer border.
|
||||
*/
|
||||
inputOuterStyle?: ViewStyle
|
||||
/**
|
||||
* Optional input style override.
|
||||
* This gives the inputs their inner characteristics and "on" background-color.
|
||||
*/
|
||||
inputInnerStyle?: ViewStyle
|
||||
/**
|
||||
* Optional detail style override.
|
||||
* See Checkbox, Radio, and Switch for more details
|
||||
*/
|
||||
inputDetailStyle?: ViewStyle
|
||||
/**
|
||||
* The position of the label relative to the action component.
|
||||
* Default: right
|
||||
*/
|
||||
labelPosition?: "left" | "right"
|
||||
/**
|
||||
* The label text to display if not using `labelTx`.
|
||||
*/
|
||||
label?: TextProps["text"]
|
||||
/**
|
||||
* Label text which is looked up via i18n.
|
||||
*/
|
||||
labelTx?: TextProps["tx"]
|
||||
/**
|
||||
* Optional label options to pass to i18n. Useful for interpolation
|
||||
* as well as explicitly setting locale or translation fallbacks.
|
||||
*/
|
||||
labelTxOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Style overrides for label text.
|
||||
*/
|
||||
labelStyle?: StyleProp<TextStyle>
|
||||
/**
|
||||
* Pass any additional props directly to the label Text component.
|
||||
*/
|
||||
LabelTextProps?: TextProps
|
||||
/**
|
||||
* The helper text to display if not using `helperTx`.
|
||||
*/
|
||||
helper?: TextProps["text"]
|
||||
/**
|
||||
* Helper text which is looked up via i18n.
|
||||
*/
|
||||
helperTx?: TextProps["tx"]
|
||||
/**
|
||||
* Optional helper options to pass to i18n. Useful for interpolation
|
||||
* as well as explicitly setting locale or translation fallbacks.
|
||||
*/
|
||||
helperTxOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Pass any additional props directly to the helper Text component.
|
||||
*/
|
||||
HelperTextProps?: TextProps
|
||||
/**
|
||||
* The input control for the type of toggle component
|
||||
*/
|
||||
ToggleInput: FC<BaseToggleInputProps<T>>
|
||||
}
|
||||
|
||||
export interface BaseToggleInputProps<T> {
|
||||
on: boolean
|
||||
status: ToggleProps<T>["status"]
|
||||
disabled: boolean
|
||||
outerStyle: ViewStyle
|
||||
innerStyle: ViewStyle
|
||||
detailStyle: ViewStyle | ImageStyle
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a boolean input.
|
||||
* This is a controlled component that requires an onValueChange callback that updates the value prop in order for the component to reflect user actions. If the value prop is not updated, the component will continue to render the supplied value prop instead of the expected result of any user actions.
|
||||
* @param {ToggleProps} props - The props for the `Toggle` component.
|
||||
* @returns {JSX.Element} The rendered `Toggle` component.
|
||||
*/
|
||||
export function Toggle<T>(props: ToggleProps<T>) {
|
||||
const {
|
||||
editable = true,
|
||||
status,
|
||||
value,
|
||||
onPress,
|
||||
onValueChange,
|
||||
labelPosition = "right",
|
||||
helper,
|
||||
helperTx,
|
||||
helperTxOptions,
|
||||
HelperTextProps,
|
||||
containerStyle: $containerStyleOverride,
|
||||
inputWrapperStyle: $inputWrapperStyleOverride,
|
||||
ToggleInput,
|
||||
accessibilityRole,
|
||||
...WrapperProps
|
||||
} = props
|
||||
|
||||
const {
|
||||
theme: { colors },
|
||||
themed,
|
||||
} = useAppTheme()
|
||||
|
||||
const disabled = editable === false || status === "disabled" || props.disabled
|
||||
|
||||
const Wrapper = useMemo(
|
||||
() => (disabled ? View : TouchableOpacity) as ComponentType<TouchableOpacityProps | ViewProps>,
|
||||
[disabled],
|
||||
)
|
||||
|
||||
const $containerStyles = [$containerStyleOverride]
|
||||
const $inputWrapperStyles = [$styles.row, $inputWrapper, $inputWrapperStyleOverride]
|
||||
const $helperStyles = themed([
|
||||
$helper,
|
||||
status === "error" && { color: colors.error },
|
||||
HelperTextProps?.style,
|
||||
])
|
||||
|
||||
/**
|
||||
* @param {GestureResponderEvent} e - The event object.
|
||||
*/
|
||||
function handlePress(e: GestureResponderEvent) {
|
||||
if (disabled) return
|
||||
onValueChange?.(!value)
|
||||
onPress?.(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
activeOpacity={1}
|
||||
accessibilityRole={accessibilityRole}
|
||||
accessibilityState={{ checked: value, disabled }}
|
||||
{...WrapperProps}
|
||||
style={$containerStyles}
|
||||
onPress={handlePress}
|
||||
>
|
||||
<View style={$inputWrapperStyles}>
|
||||
{labelPosition === "left" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}
|
||||
|
||||
<ToggleInput
|
||||
on={!!value}
|
||||
disabled={!!disabled}
|
||||
status={status}
|
||||
outerStyle={props.inputOuterStyle ?? {}}
|
||||
innerStyle={props.inputInnerStyle ?? {}}
|
||||
detailStyle={props.inputDetailStyle ?? {}}
|
||||
/>
|
||||
|
||||
{labelPosition === "right" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}
|
||||
</View>
|
||||
|
||||
{!!(helper || helperTx) && (
|
||||
<Text
|
||||
preset="formHelper"
|
||||
text={helper}
|
||||
tx={helperTx}
|
||||
txOptions={helperTxOptions}
|
||||
{...HelperTextProps}
|
||||
style={$helperStyles}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ToggleProps} props - The props for the `FieldLabel` component.
|
||||
* @returns {JSX.Element} The rendered `FieldLabel` component.
|
||||
*/
|
||||
function FieldLabel<T>(props: ToggleProps<T>) {
|
||||
const {
|
||||
status,
|
||||
label,
|
||||
labelTx,
|
||||
labelTxOptions,
|
||||
LabelTextProps,
|
||||
labelPosition,
|
||||
labelStyle: $labelStyleOverride,
|
||||
} = props
|
||||
|
||||
const {
|
||||
theme: { colors },
|
||||
themed,
|
||||
} = useAppTheme()
|
||||
|
||||
if (!label && !labelTx && !LabelTextProps?.children) return null
|
||||
|
||||
const $labelStyle = themed([
|
||||
$label,
|
||||
status === "error" && { color: colors.error },
|
||||
labelPosition === "right" && $labelRight,
|
||||
labelPosition === "left" && $labelLeft,
|
||||
$labelStyleOverride,
|
||||
LabelTextProps?.style,
|
||||
])
|
||||
|
||||
return (
|
||||
<Text
|
||||
preset="formLabel"
|
||||
text={label}
|
||||
tx={labelTx}
|
||||
txOptions={labelTxOptions}
|
||||
{...LabelTextProps}
|
||||
style={$labelStyle}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const $inputWrapper: ViewStyle = {
|
||||
alignItems: "center",
|
||||
}
|
||||
|
||||
export const $inputOuterBase: ViewStyle = {
|
||||
height: s(24),
|
||||
width: s(24),
|
||||
borderWidth: 2,
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
}
|
||||
|
||||
const $helper: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
marginTop: spacing.xs,
|
||||
})
|
||||
|
||||
const $label: TextStyle = {
|
||||
flex: 1,
|
||||
}
|
||||
|
||||
const $labelRight: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
marginStart: spacing.md,
|
||||
})
|
||||
|
||||
const $labelLeft: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
marginEnd: spacing.md,
|
||||
})
|
||||
Reference in New Issue
Block a user