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 { container } = render(<App />);
|
||||||
const search = await screen.findByLabelText("搜索对话");
|
const search = await screen.findByLabelText("搜索对话");
|
||||||
|
const searchControls = within(search.parentElement!);
|
||||||
|
expect(
|
||||||
|
searchControls.queryByRole("button", { name: "清除搜索" }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
const conversationList =
|
const conversationList =
|
||||||
container.querySelector<HTMLElement>(".conversation-list");
|
container.querySelector<HTMLElement>(".conversation-list");
|
||||||
if (!conversationList) {
|
if (!conversationList) {
|
||||||
@@ -2219,6 +2223,33 @@ describe("App", () => {
|
|||||||
).not.toBeInTheDocument();
|
).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" } });
|
fireEvent.change(search, { target: { value: "missing topic" } });
|
||||||
expect(
|
expect(
|
||||||
await within(conversationList).findByText("没有匹配的对话"),
|
await within(conversationList).findByText("没有匹配的对话"),
|
||||||
|
|||||||
@@ -2448,6 +2448,7 @@ function App(): React.JSX.Element {
|
|||||||
] = useState(true);
|
] = useState(true);
|
||||||
const [incompleteMagicTodoCount, setIncompleteMagicTodoCount] = useState(0);
|
const [incompleteMagicTodoCount, setIncompleteMagicTodoCount] = useState(0);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
const deferredSearchQuery = useDeferredValue(searchQuery);
|
const deferredSearchQuery = useDeferredValue(searchQuery);
|
||||||
const [searchConversationSnapshot, setSearchConversationSnapshot] =
|
const [searchConversationSnapshot, setSearchConversationSnapshot] =
|
||||||
useState(conversations);
|
useState(conversations);
|
||||||
@@ -8181,10 +8182,25 @@ function App(): React.JSX.Element {
|
|||||||
<Search size={15} />
|
<Search size={15} />
|
||||||
<input
|
<input
|
||||||
aria-label={t("sidebar.searchLabel")}
|
aria-label={t("sidebar.searchLabel")}
|
||||||
|
ref={searchInputRef}
|
||||||
onChange={(event) => setSearchQuery(event.target.value)}
|
onChange={(event) => setSearchQuery(event.target.value)}
|
||||||
placeholder={t("sidebar.searchPlaceholder")}
|
placeholder={t("sidebar.searchPlaceholder")}
|
||||||
value={searchQuery}
|
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>
|
</div>
|
||||||
|
|
||||||
<nav className="primary-nav" aria-label={t("navigation.label")}>
|
<nav className="primary-nav" aria-label={t("navigation.label")}>
|
||||||
|
|||||||
@@ -3373,10 +3373,12 @@ textarea:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-search {
|
.sidebar-search {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 248px;
|
min-width: 248px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 9px 11px;
|
padding: 9px 11px;
|
||||||
|
padding-right: 40px;
|
||||||
border: 1px solid var(--border-default);
|
border: 1px solid var(--border-default);
|
||||||
border-radius: var(--radius-control);
|
border-radius: var(--radius-control);
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
@@ -3386,6 +3388,7 @@ textarea:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-search input {
|
.sidebar-search input {
|
||||||
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
@@ -3398,6 +3401,19 @@ textarea:focus-visible {
|
|||||||
color: var(--text-muted);
|
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 {
|
.primary-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 248px;
|
min-width: 248px;
|
||||||
|
|||||||
Reference in New Issue
Block a user