feat: add search clear functionality and improve search input styling

This commit is contained in:
shuaiplus
2026-03-28 01:58:47 +08:00
parent 783fcbbe4b
commit bd8e26d2ab
4 changed files with 91 additions and 17 deletions
+1
View File
@@ -828,6 +828,7 @@ function folderName(id: string | null | undefined): string {
sortMenuRef={sortMenuRef}
listPanelRef={listPanelRef}
onSearchInput={setSearchInput}
onClearSearch={() => setSearchInput('')}
onSearchCompositionStart={() => setSearchComposing(true)}
onSearchCompositionEnd={(value) => {
setSearchComposing(false);
+27 -8
View File
@@ -37,6 +37,7 @@ interface VaultListPanelProps {
sortMenuRef: RefObject<HTMLDivElement>;
listPanelRef: RefObject<HTMLDivElement>;
onSearchInput: (value: string) => void;
onClearSearch: () => void;
onSearchCompositionStart: () => void;
onSearchCompositionEnd: (value: string) => void;
onToggleSortMenu: () => void;
@@ -62,14 +63,32 @@ export default function VaultListPanel(props: VaultListPanelProps) {
return (
<section className="list-col">
<div className="list-head">
<input
className="search-input"
placeholder={t('txt_search_your_secure_vault')}
value={props.searchInput}
onInput={(e) => props.onSearchInput((e.currentTarget as HTMLInputElement).value)}
onCompositionStart={props.onSearchCompositionStart}
onCompositionEnd={(e) => props.onSearchCompositionEnd((e.currentTarget as HTMLInputElement).value)}
/>
<div className="search-input-wrap">
<input
className="search-input"
placeholder={t('txt_search_your_secure_vault')}
value={props.searchInput}
onInput={(e) => props.onSearchInput((e.currentTarget as HTMLInputElement).value)}
onCompositionStart={props.onSearchCompositionStart}
onCompositionEnd={(e) => props.onSearchCompositionEnd((e.currentTarget as HTMLInputElement).value)}
onKeyDown={(e) => {
if (e.key !== 'Escape' || !props.searchInput) return;
e.preventDefault();
props.onClearSearch();
}}
/>
{!!props.searchInput && (
<button
type="button"
className="search-clear-btn"
aria-label={t('txt_clear_search')}
title={t('txt_clear_search_esc')}
onClick={props.onClearSearch}
>
<X size={14} />
</button>
)}
</div>
<div className="sort-menu-wrap" ref={props.sortMenuRef}>
<button
type="button"
+4
View File
@@ -546,6 +546,8 @@ const messages: Record<Locale, Record<string, string>> = {
txt_save_profile_failed: "Save profile failed",
txt_search_sends: "Search sends...",
txt_search_your_secure_vault: "Search your secure vault...",
txt_clear_search: "Clear search",
txt_clear_search_esc: "Clear search (Esc)",
txt_sort: "Sort",
txt_sort_last_edited: "Modified",
txt_sort_created: "Created",
@@ -872,6 +874,8 @@ const zhCNOverrides: Record<string, string> = {
txt_loading_nodewarden: '正在加载 NodeWarden...',
txt_search_sends: '搜索发送...',
txt_search_your_secure_vault: '搜索你的密码库...',
txt_clear_search: '清空搜索',
txt_clear_search_esc: '清空搜索(Esc',
txt_refresh: '刷新',
txt_sync: '同步',
txt_sync_vault: '同步',
+59 -9
View File
@@ -1021,12 +1021,15 @@ input[type='file'].input::file-selector-button:hover {
.search-input {
width: 100%;
height: 40px;
border: 1px solid var(--line);
border-radius: 12px;
padding: 0 12px;
height: 48px;
border: 1px solid rgba(74, 103, 150, 0.42);
border-radius: 14px;
padding: 10px 14px;
font-size: 16px;
outline: none;
color: var(--text);
background: var(--panel);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
transition:
border-color var(--dur-fast) var(--ease-smooth),
box-shadow var(--dur-fast) var(--ease-out-soft),
@@ -1034,13 +1037,52 @@ input[type='file'].input::file-selector-button:hover {
transform var(--dur-fast) var(--ease-out-soft);
}
.search-input-wrap {
position: relative;
flex: 1 1 auto;
min-width: 0;
}
.search-input:focus {
border-color: rgba(43, 102, 217, 0.28);
background: #fbfdff;
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.08), 0 8px 18px rgba(37, 99, 235, 0.06);
border-color: rgba(43, 102, 217, 0.6);
background-color: #fbfdff;
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.11), 0 10px 20px rgba(37, 99, 235, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.95);
transform: translateY(-1px);
}
.search-input-wrap .search-input {
padding-right: 42px;
}
.search-clear-btn {
position: absolute;
top: 50%;
right: 9px;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 999px;
background: rgba(148, 163, 184, 0.18);
color: var(--muted);
cursor: pointer;
transform: translateY(-50%);
transition: background-color var(--dur-fast) var(--ease-out-soft), color var(--dur-fast) var(--ease-out-soft), transform var(--dur-fast) var(--ease-out-soft);
}
.search-clear-btn:hover {
background: rgba(59, 130, 246, 0.18);
color: var(--brand);
transform: translateY(-50%) scale(1.04);
}
.search-clear-btn:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.16);
}
.tree-btn {
width: 100%;
min-width: 0;
@@ -1151,10 +1193,13 @@ input[type='file'].input::file-selector-button:hover {
margin-bottom: 8px;
}
.list-head .search-input {
.list-head .search-input-wrap {
flex: 1 1 auto;
min-width: 0;
height: 36px;
}
.list-head .search-input {
height: 42px;
}
.list-head .btn {
@@ -3733,6 +3778,11 @@ input[type='file'].input::file-selector-button:hover {
white-space: nowrap;
}
.list-head .search-input-wrap {
width: 100%;
min-width: 0;
}
.list-head .search-input {
width: 100%;
min-width: 0;