diff options
Diffstat (limited to 'apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx')
| -rw-r--r-- | apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx b/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx new file mode 100644 index 00000000..969e48b2 --- /dev/null +++ b/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx @@ -0,0 +1,187 @@ +import type { + NativeSyntheticEvent, + TextInputFocusEventData, +} from "react-native"; +import * as React from "react"; +import { Pressable, TextInput, View, ViewStyle } from "react-native"; +import Animated, { + measure, + useAnimatedRef, + useAnimatedStyle, + useDerivedValue, + withTiming, +} from "react-native-reanimated"; +import { Text } from "@/components/ui/Text"; +import { useColorScheme } from "@/lib/useColorScheme"; +import { cn } from "@/lib/utils"; +import { useAugmentedRef, useControllableState } from "@rn-primitives/hooks"; +import { Icon } from "@roninoss/icons"; + +import type { SearchInputProps } from "./types"; + +// Add as class when possible: https://github.com/marklawlor/nativewind/issues/522 +const BORDER_CURVE: ViewStyle = { + borderCurve: "continuous", +}; + +const SearchInput = React.forwardRef< + React.ElementRef<typeof TextInput>, + SearchInputProps +>( + ( + { + value: valueProp, + onChangeText: onChangeTextProp, + onFocus: onFocusProp, + placeholder = "Search...", + cancelText = "Cancel", + containerClassName, + iconContainerClassName, + className, + iconColor, + onCancel, + ...props + }, + ref, + ) => { + const { colors } = useColorScheme(); + const inputRef = useAugmentedRef({ ref, methods: { focus, blur, clear } }); + const [showCancel, setShowCancel] = React.useState(false); + const showCancelDerivedValue = useDerivedValue( + () => showCancel, + [showCancel], + ); + const animatedRef = useAnimatedRef(); + + const [value = "", onChangeText] = useControllableState({ + prop: valueProp, + defaultProp: valueProp ?? "", + onChange: onChangeTextProp, + }); + + const rootStyle = useAnimatedStyle(() => { + if (_WORKLET) { + // safely use measure + const measurement = measure(animatedRef); + return { + paddingRight: showCancelDerivedValue.value + ? withTiming(measurement?.width ?? cancelText.length * 11.2) + : withTiming(0), + }; + } + return { + paddingRight: showCancelDerivedValue.value + ? withTiming(cancelText.length * 11.2) + : withTiming(0), + }; + }); + const buttonStyle3 = useAnimatedStyle(() => { + if (_WORKLET) { + // safely use measure + const measurement = measure(animatedRef); + return { + position: "absolute", + right: 0, + opacity: showCancelDerivedValue.value ? withTiming(1) : withTiming(0), + transform: [ + { + translateX: showCancelDerivedValue.value + ? withTiming(0) + : measurement?.width + ? withTiming(measurement.width) + : cancelText.length * 11.2, + }, + ], + }; + } + return { + position: "absolute", + right: 0, + opacity: showCancelDerivedValue.value ? withTiming(1) : withTiming(0), + transform: [ + { + translateX: showCancelDerivedValue.value + ? withTiming(0) + : withTiming(cancelText.length * 11.2), + }, + ], + }; + }); + + function focus() { + inputRef.current?.focus(); + } + + function blur() { + inputRef.current?.blur(); + } + + function clear() { + onChangeText(""); + } + + function onFocus(e: NativeSyntheticEvent<TextInputFocusEventData>) { + setShowCancel(true); + onFocusProp?.(e); + } + + return ( + <Animated.View className="flex-row items-center" style={rootStyle}> + <Animated.View + style={BORDER_CURVE} + className={cn( + "flex-1 flex-row rounded-lg bg-card", + containerClassName, + )} + > + <View + className={cn( + "absolute bottom-0 left-0 top-0 z-50 justify-center pl-1.5", + iconContainerClassName, + )} + > + <Icon color={iconColor ?? colors.grey3} name="magnify" size={22} /> + </View> + <TextInput + ref={inputRef} + placeholder={placeholder} + className={cn( + !showCancel && "active:bg-muted/5 dark:active:bg-muted/20", + "flex-1 rounded-lg py-2 pl-8 pr-1 text-[17px] text-foreground", + className, + )} + value={value} + onChangeText={onChangeText} + onFocus={onFocus} + clearButtonMode="while-editing" + role="searchbox" + {...props} + /> + </Animated.View> + <Animated.View + ref={animatedRef} + style={buttonStyle3} + pointerEvents={!showCancel ? "none" : "auto"} + > + <Pressable + onPress={() => { + onChangeText(""); + inputRef.current?.blur(); + setShowCancel(false); + onCancel?.(); + }} + disabled={!showCancel} + pointerEvents={!showCancel ? "none" : "auto"} + className="flex-1 justify-center active:opacity-50" + > + <Text className="px-2 text-primary">{cancelText}</Text> + </Pressable> + </Animated.View> + </Animated.View> + ); + }, +); + +SearchInput.displayName = "SearchInput"; + +export { SearchInput }; |
