mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: keep search everything reachable once mail search results load
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
// The mail search's "Search everything" row was drawn over by the search and
|
||||
// folder chips once results loaded, and was gone once the field lost focus.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { searchEverythingPlacement } from '../search-everything-entry';
|
||||
|
||||
const src = fs.readFileSync(path.resolve(__dirname, '../../screens/EmailListScreen.tsx'), 'utf8');
|
||||
|
||||
describe('Search everything in the mail search', () => {
|
||||
it('sits in the panel while the field has focus', () => {
|
||||
expect(searchEverythingPlacement({ searchFocused: true, hasActiveSearchOrFilter: true })).toBe('panel');
|
||||
expect(searchEverythingPlacement({ searchFocused: true, hasActiveSearchOrFilter: false })).toBe('panel');
|
||||
});
|
||||
|
||||
it('stays reachable as a chip once the field loses focus with a search on', () => {
|
||||
expect(searchEverythingPlacement({ searchFocused: false, hasActiveSearchOrFilter: true })).toBe('chips');
|
||||
expect(searchEverythingPlacement({ searchFocused: false, hasActiveSearchOrFilter: false })).toBeNull();
|
||||
});
|
||||
|
||||
it('has a row that keeps its height, not flex: 1 in the panel column', () => {
|
||||
const row = /<Pressable\s+(?:\/\/[^\n]*\n\s*)*style=\{([^}]+)\}\s+onPress=\{openSearchEverything\}/.exec(src);
|
||||
expect(row).not.toBeNull();
|
||||
expect(row![1]).not.toContain('recentSearchMain');
|
||||
const style = /searchEverythingRow: \{([^}]*)\}/.exec(src);
|
||||
expect(style?.[1]).toMatch(/minHeight: 44/);
|
||||
expect(style?.[1]).not.toMatch(/flex: 1/);
|
||||
});
|
||||
|
||||
it('is rendered where the placement says', () => {
|
||||
expect(src).toMatch(/searchEverythingAt === 'panel' && \(/);
|
||||
expect(src).toMatch(/searchEverythingAt === 'chips' \? \(\s*<FilterChip[\s\S]{0,200}onPress=\{openSearchEverything\}/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// Where the mail search offers "Search everything" (the global search across
|
||||
// mail, contacts, events and files). It was only a row in the panel under
|
||||
// the focused field: once results loaded, the search and folder chips were
|
||||
// drawn over it (the row took flex: 1 in a column, so its height wasn't
|
||||
// counted), and when the field lost focus it was gone. The panel row keeps
|
||||
// its own height now, and while a search or filter is on and the field is
|
||||
// not focused, a chip at the front of the chip row stands in for it.
|
||||
|
||||
export type SearchEverythingPlacement = 'panel' | 'chips' | null;
|
||||
|
||||
export function searchEverythingPlacement(opts: {
|
||||
searchFocused: boolean;
|
||||
hasActiveSearchOrFilter: boolean;
|
||||
}): SearchEverythingPlacement {
|
||||
if (opts.searchFocused) return 'panel';
|
||||
return opts.hasActiveSearchOrFilter ? 'chips' : null;
|
||||
}
|
||||
@@ -89,6 +89,7 @@ import { resolveFolderRef } from '../lib/folder-ref';
|
||||
import type { Attachment, Email } from '../api/types';
|
||||
import { createFrameMonitor, logModuleTimes, perfOnce, perfStart } from '../lib/perf-marks';
|
||||
import { MODAL_ORIENTATIONS } from '../lib/modal-orientations';
|
||||
import { searchEverythingPlacement } from '../lib/search-everything-entry';
|
||||
|
||||
// The server refuses keywords per message (a read-only shared folder;
|
||||
// Stalwart 1.0: over 128 bytes, or more than 100 on one message), and the
|
||||
@@ -1075,6 +1076,11 @@ export default function EmailListScreen({
|
||||
// "Search everything": mail, contacts, events and files of every account
|
||||
// (webmail global search palette).
|
||||
const [globalSearchOpen, setGlobalSearchOpen] = React.useState(false);
|
||||
const openSearchEverything = () => {
|
||||
setSearchFocused(false);
|
||||
searchInputRef.current?.blur();
|
||||
setGlobalSearchOpen(true);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
// Keep local input in sync when the store is cleared externally
|
||||
// (e.g. clearing the search from the chips row).
|
||||
@@ -1143,6 +1149,8 @@ export default function EmailListScreen({
|
||||
(filters.keyword ? 1 : 0) +
|
||||
(filters.folder && filters.folder !== 'current' ? 1 : 0);
|
||||
const hasActiveSearchOrFilter = Boolean(storeSearchQuery) || activeFilterCount > 0;
|
||||
// In the panel under the focused field, else a chip while a search is on.
|
||||
const searchEverythingAt = searchEverythingPlacement({ searchFocused, hasActiveSearchOrFilter });
|
||||
|
||||
// Back first leaves the selection, then the search and filters; only then
|
||||
// does it leave the app (it used to exit with a search still open).
|
||||
@@ -1567,15 +1575,13 @@ export default function EmailListScreen({
|
||||
{/* Search bar (always visible; inside the header in a short window) */}
|
||||
{!shortHeight && renderSearchBar(false)}
|
||||
|
||||
{searchFocused && (
|
||||
{searchEverythingAt === 'panel' && (
|
||||
<View style={styles.recentSearches}>
|
||||
<Pressable
|
||||
style={[styles.recentSearchRow, styles.recentSearchMain]}
|
||||
onPress={() => {
|
||||
setSearchFocused(false);
|
||||
searchInputRef.current?.blur();
|
||||
setGlobalSearchOpen(true);
|
||||
}}
|
||||
// Not recentSearchMain: flex 1 in this column left the row's
|
||||
// height out, and the chips below were drawn over it.
|
||||
style={styles.searchEverythingRow}
|
||||
onPress={openSearchEverything}
|
||||
accessibilityRole="button"
|
||||
testID="search-everything"
|
||||
>
|
||||
@@ -1629,6 +1635,13 @@ export default function EmailListScreen({
|
||||
|
||||
{hasActiveSearchOrFilter && (
|
||||
<View style={styles.filterChipsRow}>
|
||||
{searchEverythingAt === 'chips' ? (
|
||||
<FilterChip
|
||||
icon={<Globe size={12} color={c.textSecondary} />}
|
||||
label={t('global_search.title', 'Search everything')}
|
||||
onPress={openSearchEverything}
|
||||
/>
|
||||
) : null}
|
||||
{storeSearchQuery ? (
|
||||
<FilterChip
|
||||
icon={<Search size={12} color={c.textSecondary} />}
|
||||
@@ -2489,6 +2502,7 @@ function makeStyles(c: ThemePalette) {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
recentSearches: {
|
||||
flexShrink: 0,
|
||||
marginHorizontal: spacing.lg,
|
||||
marginBottom: spacing.sm,
|
||||
backgroundColor: c.popover,
|
||||
@@ -2511,6 +2525,14 @@ function makeStyles(c: ThemePalette) {
|
||||
gap: spacing.sm,
|
||||
},
|
||||
recentSearchMain: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: spacing.sm },
|
||||
searchEverythingRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
minHeight: 44,
|
||||
flexShrink: 0,
|
||||
},
|
||||
recentSearchText: { ...typography.body, color: c.text, flexShrink: 1 },
|
||||
emptyFolderBanner: {
|
||||
flexDirection: 'row',
|
||||
|
||||
Reference in New Issue
Block a user