import React from 'react' import { AccessibilityProps, StyleSheet, TextInput, TextInputProps, TextStyle, View, ViewStyle, } from 'react-native' import {HITSLOP_20} from '#/lib/constants' import {mergeRefs} from '#/lib/merge-refs' import {android, atoms as a, useTheme, web} from '#/alf' import {useInteractionState} from '#/components/hooks/useInteractionState' import {Props as SVGIconProps} from '#/components/icons/common' import {Text} from '#/components/Typography' const Context = React.createContext<{ inputRef: React.RefObject | null isInvalid: boolean hovered: boolean onHoverIn: () => void onHoverOut: () => void focused: boolean onFocus: () => void onBlur: () => void }>({ inputRef: null, isInvalid: false, hovered: false, onHoverIn: () => {}, onHoverOut: () => {}, focused: false, onFocus: () => {}, onBlur: () => {}, }) export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}> export function Root({children, isInvalid = false}: RootProps) { const inputRef = React.useRef(null) const { state: hovered, onIn: onHoverIn, onOut: onHoverOut, } = useInteractionState() const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const context = React.useMemo( () => ({ inputRef, hovered, onHoverIn, onHoverOut, focused, onFocus, onBlur, isInvalid, }), [ inputRef, hovered, onHoverIn, onHoverOut, focused, onFocus, onBlur, isInvalid, ], ) return ( inputRef.current?.focus(), onMouseOver: onHoverIn, onMouseOut: onHoverOut, })}> {children} ) } export function useSharedInputStyles() { const t = useTheme() return React.useMemo(() => { const hover: ViewStyle[] = [ { borderColor: t.palette.contrast_100, }, ] const focus: ViewStyle[] = [ { backgroundColor: t.palette.contrast_50, borderColor: t.palette.primary_500, }, ] const error: ViewStyle[] = [ { backgroundColor: t.palette.negative_25, borderColor: t.palette.negative_300, }, ] const errorHover: ViewStyle[] = [ { backgroundColor: t.palette.negative_25, borderColor: t.palette.negative_500, }, ] return { chromeHover: StyleSheet.flatten(hover), chromeFocus: StyleSheet.flatten(focus), chromeError: StyleSheet.flatten(error), chromeErrorHover: StyleSheet.flatten(errorHover), } }, [t]) } export type InputProps = Omit & { label: string value?: string onChangeText?: (value: string) => void isInvalid?: boolean inputRef?: React.RefObject | React.ForwardedRef } export function createInput(Component: typeof TextInput) { return function Input({ label, placeholder, value, onChangeText, onFocus, onBlur, isInvalid, inputRef, style, ...rest }: InputProps) { const t = useTheme() const ctx = React.useContext(Context) const withinRoot = Boolean(ctx.inputRef) const {chromeHover, chromeFocus, chromeError, chromeErrorHover} = useSharedInputStyles() if (!withinRoot) { return ( ) } const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean)) return ( <> { ctx.onFocus() onFocus?.(e) }} onBlur={e => { ctx.onBlur() onBlur?.(e) }} placeholder={placeholder || label} placeholderTextColor={t.palette.contrast_500} keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} hitSlop={HITSLOP_20} style={[ a.relative, a.z_20, a.flex_1, a.text_md, t.atoms.text, a.px_xs, { // paddingVertical doesn't work w/multiline - esb paddingTop: 12, paddingBottom: 13, lineHeight: a.text_md.fontSize * 1.1875, textAlignVertical: rest.multiline ? 'top' : undefined, minHeight: rest.multiline ? 80 : undefined, minWidth: 0, }, // fix for autofill styles covering border web({ paddingTop: 10, paddingBottom: 11, marginTop: 2, marginBottom: 2, }), android({ paddingTop: 8, paddingBottom: 8, }), style, ]} /> ) } } export const Input = createInput(TextInput) export function LabelText({ nativeID, children, }: React.PropsWithChildren<{nativeID?: string}>) { const t = useTheme() return ( {children} ) } export function Icon({icon: Comp}: {icon: React.ComponentType}) { const t = useTheme() const ctx = React.useContext(Context) const {hover, focus, errorHover, errorFocus} = React.useMemo(() => { const hover: TextStyle[] = [ { color: t.palette.contrast_800, }, ] const focus: TextStyle[] = [ { color: t.palette.primary_500, }, ] const errorHover: TextStyle[] = [ { color: t.palette.negative_500, }, ] const errorFocus: TextStyle[] = [ { color: t.palette.negative_500, }, ] return { hover, focus, errorHover, errorFocus, } }, [t]) return ( ) } export function SuffixText({ children, label, accessibilityHint, }: React.PropsWithChildren<{ label: string accessibilityHint?: AccessibilityProps['accessibilityHint'] }>) { const t = useTheme() const ctx = React.useContext(Context) return ( {children} ) }