authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-17 20:19:27-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-17 20:19:27-07:00
log14337401bc801d6239dd10e34997b4481081cf1d
tree387cf062c471070fe5e12007afa84410f364b3da
parent6088c8104cfda0c11eafd1e1f636d47106fee136
signature Signed by PGP key B5690EEEBB952194

refactor(mobile): share Android toolbar search fields (#12359)


3 files changed, 79 insertions(+), 87 deletions(-)

apps/mobile/src/components/MaterialSearchField.tsx created+59
...@@ -0,0 +1,59 @@
1import type { RefObject } from "react";
2import { Pressable, TextInput, View } from "react-native";
3
4import { SymbolView } from "./AppSymbol";
5
6export function MaterialSearchField({
7 inputRef,
8 accessibilityLabel,
9 clearAccessibilityLabel,
10 placeholder,
11 value,
12 onChangeText,
13}: {
14 readonly inputRef: RefObject<TextInput | null>;
15 readonly accessibilityLabel: string;
16 readonly clearAccessibilityLabel: string;
17 readonly placeholder: string;
18 readonly value: string;
19 readonly onChangeText: (value: string) => void;
20}) {
21 return (
22 <View className="h-12 min-w-0 flex-1 flex-row items-center gap-2 rounded-full border border-input-border bg-input px-3">
23 <SymbolView name="magnifyingglass" size={18} tintColorClassName="accent-foreground-muted" />
24 <TextInput
25 ref={inputRef}
26 accessibilityLabel={accessibilityLabel}
27 autoFocus
28 autoCapitalize="none"
29 autoCorrect={false}
30 returnKeyType="search"
31 placeholder={placeholder}
32 placeholderTextColorClassName="accent-placeholder"
33 selectionColorClassName="accent-primary/32"
34 cursorColorClassName="accent-primary"
35 selectionHandleColorClassName="accent-primary"
36 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"
37 value={value}
38 onChangeText={onChangeText}
39 />
40 {value.length > 0 ? (
41 <Pressable
42 accessibilityLabel={clearAccessibilityLabel}
43 accessibilityRole="button"
44 hitSlop={10}
45 onPress={() => {
46 onChangeText("");
47 inputRef.current?.focus();
48 }}
49 >
50 <SymbolView
51 name="xmark.circle.fill"
52 size={18}
53 tintColorClassName="accent-foreground-muted"
54 />
55 </Pressable>
56 ) : null}
57 </View>
58 );
59}
apps/mobile/src/features/files/MaterialFilesHeader.tsx+10-42
...@@ -1,9 +1,9 @@...@@ -1,9 +1,9 @@
1import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";1import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
2import { BackHandler, Keyboard, Pressable, TextInput, View } from "react-native";2import { BackHandler, Keyboard, type TextInput, View } from "react-native";
3import { useSafeAreaInsets } from "react-native-safe-area-context";3import { useSafeAreaInsets } from "react-native-safe-area-context";
44
5import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader";5import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader";
6import { SymbolView } from "../../components/AppSymbol";6import { MaterialSearchField } from "../../components/MaterialSearchField";
77
8/** Keep Files search in the same header row on compact and expanded layouts. */8/** Keep Files search in the same header row on compact and expanded layouts. */
9export function MaterialFilesHeader(props: {9export function MaterialFilesHeader(props: {
...@@ -74,46 +74,14 @@ export function MaterialFilesHeader(props: {...@@ -74,46 +74,14 @@ export function MaterialFilesHeader(props: {
74 icon="arrow.left"74 icon="arrow.left"
75 onPress={closeSearch}75 onPress={closeSearch}
76 />76 />
77 <View className="h-12 min-w-0 flex-1 flex-row items-center gap-2 rounded-full border border-input-border bg-input px-3">77 <MaterialSearchField
78 <SymbolView78 inputRef={searchRef}
79 name="magnifyingglass"79 accessibilityLabel="Search files"
80 size={18}80 clearAccessibilityLabel="Clear file search"
81 tintColorClassName="accent-foreground-muted"81 placeholder="Search files"
82 />82 value={props.searchQuery}
83 <TextInput83 onChangeText={onSearchQueryChange}
84 ref={searchRef}84 />
85 accessibilityLabel="Search files"
86 autoFocus
87 autoCapitalize="none"
88 autoCorrect={false}
89 returnKeyType="search"
90 placeholder="Search files"
91 placeholderTextColorClassName="accent-placeholder"
92 selectionColorClassName="accent-primary/32"
93 cursorColorClassName="accent-primary"
94 selectionHandleColorClassName="accent-primary"
95 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"
96 value={props.searchQuery}
97 onChangeText={onSearchQueryChange}
98 />
99 {props.searchQuery.length > 0 ? (
100 <Pressable
101 accessibilityLabel="Clear file search"
102 accessibilityRole="button"
103 hitSlop={10}
104 onPress={() => {
105 onSearchQueryChange("");
106 searchRef.current?.focus();
107 }}
108 >
109 <SymbolView
110 name="xmark.circle.fill"
111 size={18}
112 tintColorClassName="accent-foreground-muted"
113 />
114 </Pressable>
115 ) : null}
116 </View>
117 </View>85 </View>
118 </View>86 </View>
119 ) : null}87 ) : null}
apps/mobile/src/features/home/MaterialThreadListToolbar.tsx+10-45
...@@ -1,12 +1,5 @@...@@ -1,12 +1,5 @@
1import { useCallback, useEffect, useRef, useState, type ComponentProps } from "react";1import { useCallback, useEffect, useRef, useState, type ComponentProps } from "react";
2import {2import { BackHandler, Keyboard, type TextInput, View, type LayoutChangeEvent } from "react-native";
3 BackHandler,
4 Keyboard,
5 Pressable,
6 TextInput,
7 View,
8 type LayoutChangeEvent,
9} from "react-native";
10import { useSafeAreaInsets } from "react-native-safe-area-context";3import { useSafeAreaInsets } from "react-native-safe-area-context";
11import type { MenuAction } from "@react-native-menu/menu";4import type { MenuAction } from "@react-native-menu/menu";
125
...@@ -15,7 +8,7 @@ import { CompactBrandTitle } from "../../components/CompactBrandTitle";...@@ -15,7 +8,7 @@ import { CompactBrandTitle } from "../../components/CompactBrandTitle";
15import { MaterialFloatingActionButton } from "../../components/MaterialFloatingActionButton";8import { MaterialFloatingActionButton } from "../../components/MaterialFloatingActionButton";
16import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu";9import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu";
17import { ControlPillMenu } from "../../components/ControlPill";10import { ControlPillMenu } from "../../components/ControlPill";
18import { SymbolView } from "../../components/AppSymbol";11import { MaterialSearchField } from "../../components/MaterialSearchField";
19import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";12import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
20import { WorkspaceConnectionTitle } from "./WorkspaceConnectionTitle";13import { WorkspaceConnectionTitle } from "./WorkspaceConnectionTitle";
21import { useWorkspaceState } from "../../state/workspace";14import { useWorkspaceState } from "../../state/workspace";
...@@ -68,42 +61,14 @@ export function MaterialThreadListToolbar(props: {...@@ -68,42 +61,14 @@ export function MaterialThreadListToolbar(props: {
68 ? "line.3.horizontal.decrease.circle.fill"61 ? "line.3.horizontal.decrease.circle.fill"
69 : "line.3.horizontal.decrease.circle";62 : "line.3.horizontal.decrease.circle";
70 const searchField = (63 const searchField = (
71 <View className="h-12 min-w-0 flex-1 flex-row items-center gap-2 rounded-full border border-input-border bg-input px-3">64 <MaterialSearchField
72 <SymbolView name="magnifyingglass" size={18} tintColorClassName="accent-foreground-muted" />65 inputRef={searchRef}
73 <TextInput66 accessibilityLabel="Search threads"
74 ref={searchRef}67 clearAccessibilityLabel="Clear search"
75 accessibilityLabel="Search threads"68 placeholder="Search"
76 autoFocus={true}69 value={props.searchQuery}
77 autoCapitalize="none"70 onChangeText={onSearchQueryChange}
78 autoCorrect={false}71 />
79 returnKeyType="search"
80 placeholder="Search"
81 placeholderTextColorClassName="accent-placeholder"
82 selectionColorClassName="accent-primary/32"
83 cursorColorClassName="accent-primary"
84 selectionHandleColorClassName="accent-primary"
85 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"
86 value={props.searchQuery}
87 onChangeText={onSearchQueryChange}
88 />
89 {props.searchQuery.length > 0 ? (
90 <Pressable
91 accessibilityLabel="Clear search"
92 accessibilityRole="button"
93 hitSlop={10}
94 onPress={() => {
95 props.onSearchQueryChange("");
96 searchRef.current?.focus();
97 }}
98 >
99 <SymbolView
100 name="xmark.circle.fill"
101 size={18}
102 tintColorClassName="accent-foreground-muted"
103 />
104 </Pressable>
105 ) : null}
106 </View>
107 );72 );
10873
109 return (74 return (