fix(mobile): keep in-note search on one row
The search field, match count, and previous, next, and close controls sit on a single bar.
This commit is contained in:
1 parent
76bfb958bf
commit
aaf3dacfb6
4 files changed
+101
-46
No files matched your search
@@ -32,6 +32,7 @@ export const Camera = createIcon("camera");
|
||||
export const Check = createIcon("check");
|
||||
export const CheckSquare = createIcon("check-square");
|
||||
export const ChevronDown = createIcon("chevron-down");
|
||||
export const ChevronUp = createIcon("chevron-up");
|
||||
export const ChevronLeft = createIcon("chevron-left");
|
||||
export const ChevronRight = createIcon("chevron-right");
|
||||
export const Code = createIcon("code");
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Image as RNImage, KeyboardAvoidingView, Platform, ScrollView, StyleShee
|
||||
import { Modal } from "react-native";
|
||||
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { SvgXml } from "react-native-svg";
|
||||
import { ActivityIndicator, ChevronDown, ChevronLeft, ChevronRight, Copy, Download, History, MoreHorizontal, Pencil, RotateCcw, Search, Share2, Sparkles, Tag, Trash2, X } from "../components/icons";
|
||||
import { ActivityIndicator, ChevronDown, ChevronLeft, ChevronUp, Copy, Download, History, MoreHorizontal, Pencil, RotateCcw, Search, Share2, Sparkles, Tag, Trash2, X } from "../components/icons";
|
||||
import { Alert, Pressable, Text, TextInput } from "../components/LocalizedText";
|
||||
import LocalTiptapEditor, { type LocalTiptapEditorRef } from "../components/LocalTiptapEditor";
|
||||
import { MobileAiAssistantModal } from "../components/MobileAiAssistantModal";
|
||||
@@ -362,13 +362,6 @@ const DetailActionSheetItem = ({ danger = false, disabled = false, icon, label,
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
const DetailActionButton = ({ children, disabled = false, label, onPress }: { children: ReactNode; disabled?: boolean; label: string; onPress: () => void }) => (
|
||||
<Pressable disabled={disabled} onPress={onPress} style={[styles.actionButton, disabled && styles.buttonDisabled]}>
|
||||
{children}
|
||||
<Text style={styles.actionButtonText}>{label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
const HighlightedMetadataText = ({
|
||||
activeIndex,
|
||||
matchOffset,
|
||||
@@ -733,6 +726,13 @@ export const MemoDetailModal = ({
|
||||
}
|
||||
setActiveMatchIndex((current) => getNextMobileNoteSearchIndex(current, direction, searchMatchCount));
|
||||
};
|
||||
const closeNoteSearch = () => {
|
||||
setSearchOpen(false);
|
||||
setSearchQuery("");
|
||||
setBodySearchMatchCount(0);
|
||||
setActiveMatchIndex(0);
|
||||
safeDomCall(() => viewerRef.current?.search("", -1));
|
||||
};
|
||||
|
||||
const closeActionsAndRun = (action: () => void) => {
|
||||
setActionsOpen(false);
|
||||
@@ -1183,41 +1183,58 @@ export const MemoDetailModal = ({
|
||||
</Text>
|
||||
{searchOpen ? (
|
||||
<View style={styles.noteSearchPanel}>
|
||||
<View style={styles.searchBox}>
|
||||
<Search color="#64748b" size={18} />
|
||||
<TextInput
|
||||
accessibilityLabel="在当前笔记内搜索"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={(value) => {
|
||||
setSearchQuery(value);
|
||||
setBodySearchMatchCount(0);
|
||||
setActiveMatchIndex(0);
|
||||
}}
|
||||
placeholder="在当前笔记内搜索"
|
||||
placeholderTextColor="#94a3b8"
|
||||
style={styles.searchInput}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<Text style={[styles.noteSearchCount, searchQuery.trim() && searchMatchCount === 0 && styles.noteSearchCountEmpty]}>{searchMatchLabel}</Text>
|
||||
</View>
|
||||
<View style={styles.richEditorSearchActions}>
|
||||
<DetailActionButton disabled={searchMatchCount === 0} label="上一个搜索结果" onPress={() => moveSearchMatch(-1)}>
|
||||
<ChevronLeft color={searchMatchCount === 0 ? "#cbd5e1" : "#0f172a"} size={16} />
|
||||
</DetailActionButton>
|
||||
<DetailActionButton disabled={searchMatchCount === 0} label="下一个搜索结果" onPress={() => moveSearchMatch(1)}>
|
||||
<ChevronRight color={searchMatchCount === 0 ? "#cbd5e1" : "#0f172a"} size={16} />
|
||||
</DetailActionButton>
|
||||
<DetailActionButton label="关闭搜索" onPress={() => {
|
||||
setSearchOpen(false);
|
||||
setSearchQuery("");
|
||||
<Search color="#64748b" size={16} />
|
||||
<TextInput
|
||||
accessibilityLabel="在当前笔记内搜索"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={(value) => {
|
||||
setSearchQuery(value);
|
||||
setBodySearchMatchCount(0);
|
||||
setActiveMatchIndex(0);
|
||||
safeDomCall(() => viewerRef.current?.search("", -1));
|
||||
}}>
|
||||
<X color="#0f172a" size={16} />
|
||||
</DetailActionButton>
|
||||
</View>
|
||||
}}
|
||||
onSubmitEditing={() => moveSearchMatch(1)}
|
||||
placeholder="搜索"
|
||||
placeholderTextColor="#94a3b8"
|
||||
returnKeyType="search"
|
||||
style={styles.noteSearchInput}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<Text
|
||||
accessibilityLiveRegion="polite"
|
||||
style={[styles.noteSearchCount, searchQuery.trim() && searchMatchCount === 0 && styles.noteSearchCountEmpty]}
|
||||
>
|
||||
{searchMatchLabel}
|
||||
</Text>
|
||||
<Pressable
|
||||
accessibilityLabel="上一个搜索结果"
|
||||
accessibilityRole="button"
|
||||
disabled={searchMatchCount === 0}
|
||||
hitSlop={4}
|
||||
onPress={() => moveSearchMatch(-1)}
|
||||
style={[styles.noteSearchIconButton, searchMatchCount === 0 && styles.buttonDisabled]}
|
||||
>
|
||||
<ChevronUp color={searchMatchCount === 0 ? "#cbd5e1" : "#0f172a"} size={18} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
accessibilityLabel="下一个搜索结果"
|
||||
accessibilityRole="button"
|
||||
disabled={searchMatchCount === 0}
|
||||
hitSlop={4}
|
||||
onPress={() => moveSearchMatch(1)}
|
||||
style={[styles.noteSearchIconButton, searchMatchCount === 0 && styles.buttonDisabled]}
|
||||
>
|
||||
<ChevronDown color={searchMatchCount === 0 ? "#cbd5e1" : "#0f172a"} size={18} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
accessibilityLabel="关闭搜索"
|
||||
accessibilityRole="button"
|
||||
hitSlop={4}
|
||||
onPress={closeNoteSearch}
|
||||
style={styles.noteSearchIconButton}
|
||||
>
|
||||
<X color="#0f172a" size={16} />
|
||||
</Pressable>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={styles.detailDivider} />
|
||||
|
||||
@@ -1949,18 +1949,41 @@ const baseWorkspaceStyles = StyleSheet.create({
|
||||
marginTop: 20,
|
||||
},
|
||||
noteSearchPanel: {
|
||||
backgroundColor: "#f8fafc",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#ffffff",
|
||||
borderColor: "#e2e8f0",
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
gap: 10,
|
||||
marginTop: 14,
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
gap: 2,
|
||||
marginTop: 12,
|
||||
minHeight: 40,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 2,
|
||||
},
|
||||
noteSearchInput: {
|
||||
color: "#0f172a",
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
minHeight: 36,
|
||||
minWidth: 0,
|
||||
paddingVertical: 0,
|
||||
},
|
||||
noteSearchIconButton: {
|
||||
alignItems: "center",
|
||||
flexShrink: 0,
|
||||
height: 36,
|
||||
justifyContent: "center",
|
||||
width: 36,
|
||||
},
|
||||
noteSearchCount: {
|
||||
color: "#64748b",
|
||||
flexShrink: 0,
|
||||
fontSize: 12,
|
||||
fontWeight: "800",
|
||||
fontVariant: ["tabular-nums"],
|
||||
fontWeight: "700",
|
||||
minWidth: 44,
|
||||
textAlign: "right",
|
||||
},
|
||||
noteSearchCountEmpty: {
|
||||
color: "#b91c1c",
|
||||
|
||||
@@ -128,6 +128,20 @@ describe("mobile app scope", () => {
|
||||
expect(localTiptapEditorSource).toContain("scrollEditorPositionIntoView(editor, match.from");
|
||||
});
|
||||
|
||||
test("keeps in-note search on one icon row", () => {
|
||||
expect(memoDetailSource).toContain('accessibilityLabel="上一个搜索结果"');
|
||||
expect(memoDetailSource).toContain('accessibilityLabel="下一个搜索结果"');
|
||||
expect(memoDetailSource).toContain('accessibilityLabel="关闭搜索"');
|
||||
expect(memoDetailSource).not.toContain("label=\"上一个搜索结果\"");
|
||||
expect(memoDetailSource).not.toContain("label=\"下一个搜索结果\"");
|
||||
expect(memoDetailSource).not.toContain("label=\"关闭搜索\"");
|
||||
const stylesSource = readFileSync(
|
||||
new URL("../apps/mobile/src/screens/workspace-styles.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
expect(stylesSource).toMatch(/noteSearchPanel: \{[\s\S]*?flexDirection: "row"/);
|
||||
});
|
||||
|
||||
test("keeps the Android editor caret visible while the keyboard viewport changes", () => {
|
||||
expect(workspaceEditorsSource).toContain("KeyboardAvoidingView");
|
||||
expect(workspaceEditorsSource).toContain('enabled={Platform.OS === "android"}');
|
||||
|
||||
Reference in new issue
Block a user