template_0205
This commit is contained in:
292
RN_TEMPLATE/app/components/TextField.tsx
Normal file
292
RN_TEMPLATE/app/components/TextField.tsx
Normal file
@@ -0,0 +1,292 @@
|
||||
import { ComponentType, forwardRef, Ref, useImperativeHandle, useRef } from "react"
|
||||
import {
|
||||
ImageStyle,
|
||||
StyleProp,
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
TextInput,
|
||||
TextInputProps,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from "react-native"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { isRTL } from "@/i18n"
|
||||
import { useAppTheme } from "@/theme/context"
|
||||
import { $styles } from "@/theme/styles"
|
||||
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
|
||||
import { s, fs } from "@/utils/responsive"
|
||||
|
||||
import { Text, TextProps } from "./Text"
|
||||
|
||||
export interface TextFieldAccessoryProps {
|
||||
style: StyleProp<ViewStyle | TextStyle | ImageStyle>
|
||||
status: TextFieldProps["status"]
|
||||
multiline: boolean
|
||||
editable: boolean
|
||||
}
|
||||
|
||||
export interface TextFieldProps extends Omit<TextInputProps, "ref"> {
|
||||
/**
|
||||
* A style modifier for different input states.
|
||||
*/
|
||||
status?: "error" | "disabled"
|
||||
/**
|
||||
* 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"]
|
||||
/**
|
||||
* 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 placeholder text to display if not using `placeholderTx`.
|
||||
*/
|
||||
placeholder?: TextProps["text"]
|
||||
/**
|
||||
* Placeholder text which is looked up via i18n.
|
||||
*/
|
||||
placeholderTx?: TextProps["tx"]
|
||||
/**
|
||||
* Optional placeholder options to pass to i18n. Useful for interpolation
|
||||
* as well as explicitly setting locale or translation fallbacks.
|
||||
*/
|
||||
placeholderTxOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Optional input style override.
|
||||
*/
|
||||
style?: StyleProp<TextStyle>
|
||||
/**
|
||||
* Style overrides for the container
|
||||
*/
|
||||
containerStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Style overrides for the input wrapper
|
||||
*/
|
||||
inputWrapperStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* An optional component to render on the right side of the input.
|
||||
* Example: `RightAccessory={(props) => <Icon icon="ladybug" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`
|
||||
* Note: It is a good idea to memoize this.
|
||||
*/
|
||||
RightAccessory?: ComponentType<TextFieldAccessoryProps>
|
||||
/**
|
||||
* An optional component to render on the left side of the input.
|
||||
* Example: `LeftAccessory={(props) => <Icon icon="ladybug" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`
|
||||
* Note: It is a good idea to memoize this.
|
||||
*/
|
||||
LeftAccessory?: ComponentType<TextFieldAccessoryProps>
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that allows for the entering and editing of text.
|
||||
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/TextField/}
|
||||
* @param {TextFieldProps} props - The props for the `TextField` component.
|
||||
* @returns {JSX.Element} The rendered `TextField` component.
|
||||
*/
|
||||
export const TextField = forwardRef(function TextField(props: TextFieldProps, ref: Ref<TextInput>) {
|
||||
const {
|
||||
labelTx,
|
||||
label,
|
||||
labelTxOptions,
|
||||
placeholderTx,
|
||||
placeholder,
|
||||
placeholderTxOptions,
|
||||
helper,
|
||||
helperTx,
|
||||
helperTxOptions,
|
||||
status,
|
||||
RightAccessory,
|
||||
LeftAccessory,
|
||||
HelperTextProps,
|
||||
LabelTextProps,
|
||||
style: $inputStyleOverride,
|
||||
containerStyle: $containerStyleOverride,
|
||||
inputWrapperStyle: $inputWrapperStyleOverride,
|
||||
...TextInputProps
|
||||
} = props
|
||||
const input = useRef<TextInput>(null)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
themed,
|
||||
theme: { colors },
|
||||
} = useAppTheme()
|
||||
|
||||
const disabled = TextInputProps.editable === false || status === "disabled"
|
||||
|
||||
const placeholderContent = placeholderTx ? t(placeholderTx, placeholderTxOptions) : placeholder
|
||||
|
||||
const $containerStyles = [$containerStyleOverride]
|
||||
|
||||
const $labelStyles = [$labelStyle, LabelTextProps?.style]
|
||||
|
||||
const $inputWrapperStyles = [
|
||||
$styles.row,
|
||||
$inputWrapperStyle,
|
||||
status === "error" && { borderColor: colors.error },
|
||||
TextInputProps.multiline && { minHeight: s(112) },
|
||||
LeftAccessory && { paddingStart: 0 },
|
||||
RightAccessory && { paddingEnd: 0 },
|
||||
$inputWrapperStyleOverride,
|
||||
]
|
||||
|
||||
const $inputStyles: ThemedStyleArray<TextStyle> = [
|
||||
$inputStyle,
|
||||
disabled && { color: colors.textDim },
|
||||
isRTL && { textAlign: "right" as TextStyle["textAlign"] },
|
||||
TextInputProps.multiline && { height: "auto" },
|
||||
$inputStyleOverride,
|
||||
]
|
||||
|
||||
const $helperStyles = [
|
||||
$helperStyle,
|
||||
status === "error" && { color: colors.error },
|
||||
HelperTextProps?.style,
|
||||
]
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function focusInput() {
|
||||
if (disabled) return
|
||||
|
||||
input.current?.focus()
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => input.current as TextInput)
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
style={$containerStyles}
|
||||
onPress={focusInput}
|
||||
accessibilityState={{ disabled }}
|
||||
>
|
||||
{!!(label || labelTx) && (
|
||||
<Text
|
||||
preset="formLabel"
|
||||
text={label}
|
||||
tx={labelTx}
|
||||
txOptions={labelTxOptions}
|
||||
{...LabelTextProps}
|
||||
style={themed($labelStyles)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={themed($inputWrapperStyles)}>
|
||||
{!!LeftAccessory && (
|
||||
<LeftAccessory
|
||||
style={themed($leftAccessoryStyle)}
|
||||
status={status}
|
||||
editable={!disabled}
|
||||
multiline={TextInputProps.multiline ?? false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TextInput
|
||||
ref={input}
|
||||
underlineColorAndroid={colors.transparent}
|
||||
textAlignVertical="top"
|
||||
placeholder={placeholderContent}
|
||||
placeholderTextColor={colors.textDim}
|
||||
{...TextInputProps}
|
||||
editable={!disabled}
|
||||
style={themed($inputStyles)}
|
||||
/>
|
||||
|
||||
{!!RightAccessory && (
|
||||
<RightAccessory
|
||||
style={themed($rightAccessoryStyle)}
|
||||
status={status}
|
||||
editable={!disabled}
|
||||
multiline={TextInputProps.multiline ?? false}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!!(helper || helperTx) && (
|
||||
<Text
|
||||
preset="formHelper"
|
||||
text={helper}
|
||||
tx={helperTx}
|
||||
txOptions={helperTxOptions}
|
||||
{...HelperTextProps}
|
||||
style={themed($helperStyles)}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
})
|
||||
|
||||
const $labelStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
marginBottom: spacing.xs,
|
||||
})
|
||||
|
||||
const $inputWrapperStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
||||
alignItems: "flex-start",
|
||||
borderWidth: 1,
|
||||
borderRadius: s(4),
|
||||
backgroundColor: colors.palette.neutral200,
|
||||
borderColor: colors.palette.neutral400,
|
||||
overflow: "hidden",
|
||||
})
|
||||
|
||||
const $inputStyle: ThemedStyle<TextStyle> = ({ colors, typography, spacing }) => ({
|
||||
flex: 1,
|
||||
alignSelf: "stretch",
|
||||
fontFamily: typography.primary.normal,
|
||||
color: colors.text,
|
||||
fontSize: fs(16),
|
||||
height: s(24),
|
||||
// https://github.com/facebook/react-native/issues/21720#issuecomment-532642093
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
marginVertical: spacing.xs,
|
||||
marginHorizontal: spacing.sm,
|
||||
})
|
||||
|
||||
const $helperStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
marginTop: spacing.xs,
|
||||
})
|
||||
|
||||
const $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
marginEnd: spacing.xs,
|
||||
height: s(40),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
})
|
||||
|
||||
const $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
marginStart: spacing.xs,
|
||||
height: s(40),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
})
|
||||
Reference in New Issue
Block a user