Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 22x 22x 21x 22x 6x 1x 1x 5x 1x 1x 4x 1x 1x 3x 1x 1x 2x 1x 1x 1x 1x 1x 22x 21x 22x 1x | import { useEffect } from 'react';
import {
X, ChevronUp, ChevronDown,
CaseSensitive, WholeWord, Regex,
} from 'lucide-react';
import type { SearchState, SearchActions } from './useSearch';
interface SearchWidgetProps {
state: SearchState;
actions: SearchActions;
searchInputRef: React.RefObject<HTMLInputElement>;
}
export function SearchWidget({ state, actions, searchInputRef }: SearchWidgetProps) {
useEffect(() => {
if (state.isOpen) {
requestAnimationFrame(() => searchInputRef.current?.focus());
}
}, [state.isOpen, searchInputRef]);
const handleSearchKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
actions.close();
} else if (e.key === 'Enter' && e.shiftKey) {
e.preventDefault();
actions.findPrev();
} else if (e.key === 'Enter') {
e.preventDefault();
actions.findNext();
} else if (e.altKey && (e.key === 'c' || e.key === 'C')) {
e.preventDefault();
actions.toggleCaseSensitive();
} else if (e.altKey && (e.key === 'w' || e.key === 'W')) {
e.preventDefault();
actions.toggleWholeWord();
} else Eif (e.altKey && (e.key === 'r' || e.key === 'R')) {
e.preventDefault();
actions.toggleRegex();
}
};
if (!state.isOpen) return null;
const matchLabel = state.query
? state.matchCount === 0
? '无结果'
: `${state.currentIndex + 1} / ${state.matchCount}`
: '';
return (
<div className="vd-search-widget" role="search" aria-label="搜索">
<div className="vd-search-widget-inner">
<div className="vd-search-rows">
<div className="vd-search-row">
<div className="vd-search-input-wrap">
<input
ref={searchInputRef}
className="vd-search-input"
type="text"
placeholder="搜索"
value={state.query}
onChange={e => actions.setQuery(e.target.value)}
onKeyDown={handleSearchKeyDown}
spellCheck={false}
autoComplete="off"
/>
<div className="vd-search-options">
<button
className={`vd-search-option-btn ${state.caseSensitive ? 'vd-search-option-btn--active' : ''}`}
onClick={actions.toggleCaseSensitive}
title="区分大小写 (Alt+C)"
aria-pressed={state.caseSensitive}
>
<CaseSensitive size={14} />
</button>
<button
className={`vd-search-option-btn ${state.wholeWord ? 'vd-search-option-btn--active' : ''}`}
onClick={actions.toggleWholeWord}
title="全词匹配 (Alt+W)"
aria-pressed={state.wholeWord}
>
<WholeWord size={14} />
</button>
<button
className={`vd-search-option-btn ${state.useRegex ? 'vd-search-option-btn--active' : ''}`}
onClick={actions.toggleRegex}
title="使用正则表达式 (Alt+R)"
aria-pressed={state.useRegex}
>
<Regex size={14} />
</button>
</div>
</div>
<span className="vd-search-count">{matchLabel}</span>
<button className="vd-search-nav-btn" onClick={actions.findPrev} title="上一个匹配 (Shift+Enter)" disabled={state.matchCount === 0}>
<ChevronUp size={16} />
</button>
<button className="vd-search-nav-btn" onClick={actions.findNext} title="下一个匹配 (Enter)" disabled={state.matchCount === 0}>
<ChevronDown size={16} />
</button>
<button className="vd-search-nav-btn" onClick={actions.close} title="关闭 (Escape)">
<X size={16} />
</button>
</div>
</div>
</div>
</div>
);
}
|