mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-09-22 08:01:31 +00:00
style(saved-connections): implement expandable search functionality with keyboard navigation (#548)
- Added a new state for managing search expansion and a ref for the search input. - Implemented focus and selection behavior for the search input when expanded. - Enhanced keyboard navigation to handle Escape key for collapsing the search. - Updated the search input to be read-only until focused, allowing for a cleaner UI. - Introduced a clear search button that resets the filter text and collapses the search when clicked.
This commit is contained in:
@@ -133,7 +133,9 @@ export default function SavedConnections({
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
const [selectedConnectionIds, setSelectedConnectionIds] = useState<Set<string>>(new Set());
|
||||
const [filterText, setFilterText] = useState("");
|
||||
const [isSearchExpanded, setIsSearchExpanded] = useState(false);
|
||||
const [keyboardActiveConnectionId, setKeyboardActiveConnectionId] = useState<string | null>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const searchExpandedBaseRef = useRef<Set<string> | null>(null);
|
||||
const searchAutoExpandedGroupIdsRef = useRef<Set<string>>(new Set());
|
||||
const previousKeywordRef = useRef("");
|
||||
@@ -175,6 +177,19 @@ export default function SavedConnections({
|
||||
[savedConnections],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchExpanded) return;
|
||||
|
||||
const frame = window.requestAnimationFrame(() => {
|
||||
const input = searchInputRef.current;
|
||||
if (!input) return;
|
||||
input.focus();
|
||||
input.select();
|
||||
});
|
||||
|
||||
return () => window.cancelAnimationFrame(frame);
|
||||
}, [isSearchExpanded]);
|
||||
|
||||
// ── Derived tree ──────────────────────────────────────────────────────────
|
||||
const { rootNodes, ungrouped } = useMemo(() => {
|
||||
const filtered = keyword
|
||||
@@ -606,9 +621,21 @@ export default function SavedConnections({
|
||||
};
|
||||
|
||||
const handleSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!keyword || visibleConnectionIds.length === 0) return;
|
||||
if (event.nativeEvent.isComposing || event.key === "Process") return;
|
||||
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (filterText) {
|
||||
setFilterText("");
|
||||
} else {
|
||||
setIsSearchExpanded(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keyword || visibleConnectionIds.length === 0) return;
|
||||
|
||||
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -1388,7 +1415,7 @@ export default function SavedConnections({
|
||||
/>
|
||||
|
||||
<div
|
||||
className="nyaterm-wallpaper-transparent-surface flex items-center gap-1.5 px-2 py-1.5 shrink-0 border-b"
|
||||
className="nyaterm-wallpaper-transparent-surface relative flex items-center gap-1.5 px-2 py-1.5 min-h-10 shrink-0 border-b"
|
||||
style={{
|
||||
borderColor: "color-mix(in srgb, var(--df-border) 40%, transparent)",
|
||||
backgroundColor: "var(--df-bg-section-header)",
|
||||
@@ -1399,26 +1426,16 @@ export default function SavedConnections({
|
||||
<input
|
||||
type="text"
|
||||
value={filterText}
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
readOnly
|
||||
onFocus={() => setIsSearchExpanded(true)}
|
||||
onClick={() => setIsSearchExpanded(true)}
|
||||
placeholder={t("savedConnections.filter")}
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
className="w-full pl-8 pr-7 py-1 h-7 text-xs rounded-md bg-[var(--df-bg-hover)] border border-transparent outline-none transition-all placeholder:text-[var(--df-text-dimmed)] focus:bg-transparent focus:border-[var(--df-primary)] focus:ring-1 focus:ring-[var(--df-primary)] text-[var(--df-text)]"
|
||||
aria-label={t("savedConnections.search")}
|
||||
className="w-full pl-8 pr-3 py-1 h-7 text-xs rounded-md bg-[var(--df-bg-hover)] border border-transparent outline-none transition-all cursor-text placeholder:text-[var(--df-text-dimmed)] focus:bg-transparent focus:border-[var(--df-primary)] focus:ring-1 focus:ring-[var(--df-primary)] text-[var(--df-text)]"
|
||||
/>
|
||||
{filterText && (
|
||||
<button
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 p-0.5 rounded transition-colors hover:text-[var(--df-text)] text-[var(--df-text-dimmed)]"
|
||||
onClick={() => setFilterText("")}
|
||||
>
|
||||
<MdClose className="text-xs" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-0.5 shrink-0">
|
||||
<div className="ml-auto flex items-center gap-0.5 shrink-0">
|
||||
<HeaderActionButton
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@@ -1545,6 +1562,50 @@ export default function SavedConnections({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{isSearchExpanded && (
|
||||
<div
|
||||
className="nyaterm-wallpaper-control-surface absolute inset-x-2 top-1.5 bottom-1.5 z-20 flex items-center gap-1 rounded-md border px-1.5 shadow-sm"
|
||||
style={{
|
||||
backgroundColor: "var(--df-bg-panel)",
|
||||
borderColor: "var(--df-primary)",
|
||||
}}
|
||||
>
|
||||
<MdSearch className="h-4 w-4 shrink-0 translate-y-px text-primary" />
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
value={filterText}
|
||||
onChange={(event) => setFilterText(event.target.value)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
placeholder={t("savedConnections.filter")}
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
className="h-full min-w-0 flex-1 bg-transparent px-1 text-xs text-[var(--df-text)] outline-none placeholder:text-[var(--df-text-dimmed)]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-[var(--df-text-dimmed)] transition-colors hover:bg-[var(--df-bg-hover)] hover:text-[var(--df-text)]"
|
||||
aria-label={
|
||||
filterText
|
||||
? t("savedConnections.clearSearch")
|
||||
: t("common.close")
|
||||
}
|
||||
onClick={() => {
|
||||
if (filterText) {
|
||||
setFilterText("");
|
||||
searchInputRef.current?.focus();
|
||||
} else {
|
||||
setIsSearchExpanded(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MdClose className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
|
||||
Reference in New Issue
Block a user