aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-08-26 15:47:05 +0300
committerGitHub <noreply@github.com>2025-08-26 13:47:05 +0100
commited86f7ef012fb558fe8a8974e1e162ce75cbfd15 (patch)
treea3470b0e1a01aede90b75bc61eeba2545e51fe83 /apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx
parentec56ea33b5e37d02e87e480da305038a5ce7de49 (diff)
downloadkarakeep-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.tsx187
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 };