diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-08-26 15:47:05 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-26 13:47:05 +0100 |
| commit | ed86f7ef012fb558fe8a8974e1e162ce75cbfd15 (patch) | |
| tree | a3470b0e1a01aede90b75bc61eeba2545e51fe83 /apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx | |
| parent | ec56ea33b5e37d02e87e480da305038a5ce7de49 (diff) | |
| download | karakeep-ed86f7ef012fb558fe8a8974e1e162ce75cbfd15.tar.zst | |
feat(mobile): Retheme the mobile app (#1872)
* Add nativewindui
* migrate to nativewindui text
* Replace buttons with nativewindui buttons
* Use nativewindui search input
* fix the divider color
* More changes
* fix manage tag icon
* fix styling of bookmark card
* fix ios compilation
* fix search clear
* fix tag pill border color
* Store theme setting in app settings
* fix setting color appearance
* fix coloring of search input
* fix following system theme
* add a save button to info
* fix the grey colors on android
* fix icon active tint color
* drop the use of TextField
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 }; |
