fix: add inline clear action to conversation search
Show a clear button when the conversation search contains text. Clearing restores the scoped conversation list and returns focus to the input, with regression coverage for visibility and filtering. Release note: Conversation search now includes an X button to clear keywords and return to the current project's conversations.
This commit is contained in:
@@ -2183,6 +2183,10 @@ describe("App", () => {
|
||||
]);
|
||||
const { container } = render(<App />);
|
||||
const search = await screen.findByLabelText("搜索对话");
|
||||
const searchControls = within(search.parentElement!);
|
||||
expect(
|
||||
searchControls.queryByRole("button", { name: "清除搜索" }),
|
||||
).not.toBeInTheDocument();
|
||||
const conversationList =
|
||||
container.querySelector<HTMLElement>(".conversation-list");
|
||||
if (!conversationList) {
|
||||
@@ -2219,6 +2223,33 @@ describe("App", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
const clearSearch = searchControls.getByRole("button", {
|
||||
name: "清除搜索",
|
||||
});
|
||||
clearSearch.focus();
|
||||
fireEvent.click(clearSearch);
|
||||
expect(search).toHaveValue("");
|
||||
expect(search).toHaveFocus();
|
||||
expect(
|
||||
searchControls.queryByRole("button", { name: "清除搜索" }),
|
||||
).not.toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
within(conversationList).getByText("标题里的 Alpha"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(conversationList).getByText("正文命中的会话"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(conversationList).queryByText("其他项目里的 Alpha"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
fireEvent.change(search, { target: { value: " " } });
|
||||
fireEvent.click(
|
||||
searchControls.getByRole("button", { name: "清除搜索" }),
|
||||
);
|
||||
expect(search).toHaveValue("");
|
||||
|
||||
fireEvent.change(search, { target: { value: "missing topic" } });
|
||||
expect(
|
||||
await within(conversationList).findByText("没有匹配的对话"),
|
||||
|
||||
@@ -2448,6 +2448,7 @@ function App(): React.JSX.Element {
|
||||
] = useState(true);
|
||||
const [incompleteMagicTodoCount, setIncompleteMagicTodoCount] = useState(0);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const deferredSearchQuery = useDeferredValue(searchQuery);
|
||||
const [searchConversationSnapshot, setSearchConversationSnapshot] =
|
||||
useState(conversations);
|
||||
@@ -8181,10 +8182,25 @@ function App(): React.JSX.Element {
|
||||
<Search size={15} />
|
||||
<input
|
||||
aria-label={t("sidebar.searchLabel")}
|
||||
ref={searchInputRef}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
placeholder={t("sidebar.searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
/>
|
||||
{searchQuery.length > 0 && (
|
||||
<button
|
||||
aria-label={t("conversation.clearSearch")}
|
||||
className="icon-button sidebar-search__clear"
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
searchInputRef.current?.focus();
|
||||
}}
|
||||
title={t("conversation.clearSearch")}
|
||||
type="button"
|
||||
>
|
||||
<X aria-hidden="true" size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="primary-nav" aria-label={t("navigation.label")}>
|
||||
|
||||
@@ -3373,10 +3373,12 @@ textarea:focus-visible {
|
||||
}
|
||||
|
||||
.sidebar-search {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 248px;
|
||||
align-items: center;
|
||||
padding: 9px 11px;
|
||||
padding-right: 40px;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-control);
|
||||
margin-bottom: 15px;
|
||||
@@ -3386,6 +3388,7 @@ textarea:focus-visible {
|
||||
}
|
||||
|
||||
.sidebar-search input {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
@@ -3398,6 +3401,19 @@ textarea:focus-visible {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.sidebar-search > svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-search .sidebar-search__clear {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--space-2);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.primary-nav {
|
||||
display: flex;
|
||||
min-width: 248px;
|
||||
|
||||
Reference in New Issue
Block a user