| author | |
| committer | |
| log | 14337401bc801d6239dd10e34997b4481081cf1d |
| tree | 387cf062c471070fe5e12007afa84410f364b3da |
| parent | 6088c8104cfda0c11eafd1e1f636d47106fee136 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 79 insertions(+), 87 deletions(-)
apps/mobile/src/components/MaterialSearchField.tsx created+59| ... | @@ -0,0 +1,59 @@ | ||
| 1 | import type { RefObject } from "react"; | ||
| 2 | import { Pressable, TextInput, View } from "react-native"; | ||
| 3 | |||
| 4 | import { SymbolView } from "./AppSymbol"; | ||
| 5 | |||
| 6 | export 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 @@ |
| 1 | import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; | 1 | import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; |
| 2 | import { BackHandler, Keyboard, Pressable, TextInput, View } from "react-native"; | 2 | import { BackHandler, Keyboard, type TextInput, View } from "react-native"; |
| 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 4 | 4 | ||
| 5 | import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader"; | 5 | import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader"; |
| 6 | import { SymbolView } from "../../components/AppSymbol"; | 6 | import { MaterialSearchField } from "../../components/MaterialSearchField"; |
| 7 | 7 | ||
| 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. */ |
| 9 | export function MaterialFilesHeader(props: { | 9 | export 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 | <SymbolView | 78 | 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 | <TextInput | 83 | 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 @@ |
| 1 | import { useCallback, useEffect, useRef, useState, type ComponentProps } from "react"; | 1 | import { useCallback, useEffect, useRef, useState, type ComponentProps } from "react"; |
| 2 | import { | 2 | import { 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"; | ||
| 10 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 11 | import type { MenuAction } from "@react-native-menu/menu"; | 4 | import type { MenuAction } from "@react-native-menu/menu"; |
| 12 | 5 | ||
| ... | @@ -15,7 +8,7 @@ import { CompactBrandTitle } from "../../components/CompactBrandTitle"; | ... | @@ -15,7 +8,7 @@ import { CompactBrandTitle } from "../../components/CompactBrandTitle"; |
| 15 | import { MaterialFloatingActionButton } from "../../components/MaterialFloatingActionButton"; | 8 | import { MaterialFloatingActionButton } from "../../components/MaterialFloatingActionButton"; |
| 16 | import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu"; | 9 | import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu"; |
| 17 | import { ControlPillMenu } from "../../components/ControlPill"; | 10 | import { ControlPillMenu } from "../../components/ControlPill"; |
| 18 | import { SymbolView } from "../../components/AppSymbol"; | 11 | import { MaterialSearchField } from "../../components/MaterialSearchField"; |
| 19 | import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; | 12 | import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; |
| 20 | import { WorkspaceConnectionTitle } from "./WorkspaceConnectionTitle"; | 13 | import { WorkspaceConnectionTitle } from "./WorkspaceConnectionTitle"; |
| 21 | import { useWorkspaceState } from "../../state/workspace"; | 14 | import { 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 | <TextInput | 66 | 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 | ); |
| 108 | 73 | ||
| 109 | return ( | 74 | return ( |